66from __future__ import annotations
77
88import argparse
9+ import os
910import pathlib
1011import sys
1112import zipfile
1213
1314
15+ def resolve_workspace_root (start : pathlib .Path ) -> pathlib .Path :
16+ current = start
17+ for candidate in [current , * current .parents ]:
18+ if (candidate / "Cargo.toml" ).exists () and (candidate / "plugins" ).exists ():
19+ return candidate
20+ raise FileNotFoundError ("Unable to locate workspace root (Cargo.toml + plugins directory)" )
21+
22+
23+ def resolve_plugin_dir (package : str , root_dir : pathlib .Path ) -> pathlib .Path :
24+ plugins_root = root_dir / "plugins"
25+ for candidate in plugins_root .glob ("*/Cargo.toml" ):
26+ try :
27+ manifest = candidate .read_text (encoding = "utf-8" )
28+ except OSError :
29+ continue
30+ in_package = False
31+ for line in manifest .splitlines ():
32+ stripped = line .strip ()
33+ if not stripped or stripped .startswith ("#" ):
34+ continue
35+ if stripped .startswith ("[" ):
36+ in_package = stripped == "[package]"
37+ continue
38+ if not in_package :
39+ continue
40+ if stripped .startswith ("name" ) and "=" in stripped :
41+ _ , value = stripped .split ("=" , 1 )
42+ value = value .split ("#" , 1 )[0 ].strip ().strip ('"' )
43+ if value == package :
44+ return candidate .parent
45+ break
46+ raise FileNotFoundError (f"Unable to find plugin directory for package '{ package } '" )
47+
48+
1449def build_archive (
1550 package : str ,
1651 version : str ,
@@ -21,14 +56,71 @@ def build_archive(
2156 if not release_dir .exists ():
2257 raise FileNotFoundError (f"release directory does not exist: { release_dir } " )
2358
59+ root_dir = resolve_workspace_root (pathlib .Path .cwd ())
60+ plugin_dir = resolve_plugin_dir (package , root_dir )
61+
2462 package_dir = output_dir / package / version
2563 package_dir .mkdir (parents = True , exist_ok = True )
2664 archive_path = package_dir / f"{ target } .zip"
2765
28- with zipfile .ZipFile (archive_path , "w" , zipfile .ZIP_DEFLATED ) as zf :
29- for path in release_dir .rglob ("*" ):
66+ expected_names = [
67+ package ,
68+ package .replace ("-" , "_" ),
69+ package .replace ("_" , "-" ),
70+ ]
71+ binaries = []
72+ seen = set ()
73+ for path in sorted (release_dir .iterdir ()):
74+ if not path .is_file ():
75+ continue
76+ stem = path .stem if path .suffix else path .name
77+ if stem in expected_names and stem not in seen :
78+ binaries .append (path )
79+ seen .add (stem )
80+
81+ if not binaries :
82+ for path in sorted (release_dir .iterdir ()):
83+ if not path .is_file ():
84+ continue
85+ if os .name == "nt" :
86+ if path .suffix .lower () == ".exe" :
87+ binaries .append (path )
88+ else :
89+ if os .access (path , os .X_OK ) and not path .suffix :
90+ binaries .append (path )
91+
92+ if not binaries :
93+ for path in sorted (release_dir .iterdir ()):
3094 if path .is_file ():
31- zf .write (path , path .relative_to (release_dir ))
95+ binaries .append (path )
96+ if not binaries :
97+ raise FileNotFoundError (f"No files found to include in archive for { package } ({ release_dir } )" )
98+
99+ extra_files = []
100+ readme_path = plugin_dir / "README.md"
101+ if readme_path .exists ():
102+ extra_files .append (readme_path )
103+
104+ license_names = ["LICENSE" , "LICENSE-MIT" , "LICENSE-APACHE" , "COPYING" ]
105+ license_added = False
106+ for candidate in license_names :
107+ candidate_path = plugin_dir / candidate
108+ if candidate_path .exists ():
109+ extra_files .append (candidate_path )
110+ license_added = True
111+ if not license_added :
112+ for candidate in license_names :
113+ candidate_path = root_dir / candidate
114+ if candidate_path .exists ():
115+ extra_files .append (candidate_path )
116+ license_added = True
117+ break
118+
119+ with zipfile .ZipFile (archive_path , "w" , zipfile .ZIP_DEFLATED ) as zf :
120+ for binary_path in binaries :
121+ zf .write (binary_path , binary_path .name )
122+ for extra_path in extra_files :
123+ zf .write (extra_path , extra_path .name )
32124
33125 return archive_path
34126
0 commit comments