2026-10-20 06:45:30 -04:00
"""Build a clean deployment zip from current workspace files, including new files."""
import argparse , hashlib , json , zipfile
from pathlib import Path
ROOT = Path ( __file__ ) . resolve () . parents [ 1 ]
parser = argparse . ArgumentParser (); parser . add_argument ( 'output' , type = Path ); args = parser . parse_args ()
output = args . output . resolve ()
if output . exists (): raise SystemExit ( 'Output already exists; choose a new release filename.' )
files = []
for directory in [ 'classes' , 'css' , 'images' , 'js' , 'html' , 'migrations' , 'reports' , 'vendor' , 'views' , 'xsl' ]:
for file in ( ROOT / directory ) . rglob ( '*' ):
if file . is_file () and not file . is_symlink () and file . suffix . lower () not in [ '.sql' , '.php' , '.js' , '.css' , '.xsl' ] and directory not in [ 'vendor' , 'images' , 'css' , 'html' ]: continue
if file . is_file () and not file . is_symlink () and not any ( p . startswith ( '.' ) for p in file . relative_to ( ROOT ) . parts ) and '_orig.' not in file . name : files . append ( file )
2026-10-21 06:53:45 -04:00
for name in [ 'main.php' , '.htaccess' , 'web.config' , 'composer.json' , 'composer.lock' , 'THIRD-PARTY-NOTICES.md' , 'browser-dependencies.json' , 'xml/settings.xml' , 'xml/constants.xml' ]:
2026-10-20 06:45:30 -04:00
files . append ( ROOT / name )
2026-10-21 06:53:45 -04:00
for name in [ 'migrate-auth.php' , 'migrate-roster.php' , 'migrate-production.php' , 'migrate-tenants.php' , 'migrate-security-capacity.php' , 'cleanup-auth.php' , 'check-production.py' , 'backup-database.php' , 'check-health.php' ]:
2026-10-20 06:45:30 -04:00
file = ROOT / 'bin' / name
if file . exists (): files . append ( file )
if not ( ROOT / 'vendor/autoload.php' ) . is_file (): raise SystemExit ( 'Install locked Composer dependencies before building a release.' )
manifest = { file . relative_to ( ROOT ) . as_posix (): hashlib . sha256 ( file . read_bytes ()) . hexdigest () for file in sorted ( set ( files ))}
for name in manifest :
if name . startswith (( '.tools/' , '.git/' , 'tests/' , 'nbproject/' )) or name . endswith ( 'settings.local.xml' ): raise SystemExit ( 'Private file would enter the release.' )
output . parent . mkdir ( parents = True , exist_ok = True )
with zipfile . ZipFile ( output , 'x' , zipfile . ZIP_DEFLATED ) as archive :
for name in manifest : archive . writestr ( name ,( ROOT / name ) . read_bytes ())
archive . writestr ( 'release-manifest.json' , json . dumps ( manifest , indent = 2 ))
print ( json . dumps ({ 'release' : str ( output ), 'files' : len ( manifest ), 'sha256' : hashlib . sha256 ( output . read_bytes ()) . hexdigest ()}))