Skip to content

Commit 5417a78

Browse files
author
Taniya Mathur
committed
adding linting command line and ui validation in paraellel
1 parent 6254678 commit 5417a78

File tree

1 file changed

+118
-36
lines changed

1 file changed

+118
-36
lines changed

publish.py

Lines changed: 118 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,16 @@ def __init__(self, verbose=False):
5858
self.cf_client = None
5959
self._is_lib_changed = False
6060
self.skip_validation = False
61+
self.lint_enabled = True
6162

6263
def clean_checksums(self):
63-
"""Delete all .checksum files in main, patterns, options, and lib directories"""
64+
"""Delete all .checksum files in main, patterns, options, lib, and ui directories"""
6465
self.console.print("[yellow]🧹 Cleaning all .checksum files...[/yellow]")
6566

6667
checksum_paths = [
6768
".checksum", # main
6869
"lib/.checksum", # lib
70+
"src/ui/.checksum", # ui
6971
]
7072

7173
# Add patterns checksum files
@@ -225,7 +227,7 @@ def print_usage(self):
225227
"""Print usage information with Rich formatting"""
226228
self.console.print("\n[bold cyan]Usage:[/bold cyan]")
227229
self.console.print(
228-
" python3 publish.py <cfn_bucket_basename> <cfn_prefix> <region> [public] [--max-workers N] [--verbose] [--no-validate] [--clean-build]"
230+
" python3 publish.py <cfn_bucket_basename> <cfn_prefix> <region> [public] [--max-workers N] [--verbose] [--no-validate] [--clean-build] [--lint on|off]"
229231
)
230232

231233
self.console.print("\n[bold cyan]Parameters:[/bold cyan]")
@@ -252,6 +254,9 @@ def print_usage(self):
252254
self.console.print(
253255
" [yellow][--clean-build][/yellow]: Optional. Delete all .checksum files to force full rebuild"
254256
)
257+
self.console.print(
258+
" [yellow][--lint on|off][/yellow]: Optional. Enable/disable UI linting and build validation (default: on)"
259+
)
255260

256261
def check_parameters(self, args):
257262
"""Check and validate input parameters"""
@@ -314,6 +319,19 @@ def check_parameters(self, args):
314319
self.console.print(
315320
"[yellow]CloudFormation template validation will be skipped[/yellow]"
316321
)
322+
elif arg == "--lint":
323+
if i + 1 >= len(remaining_args):
324+
self.console.print(
325+
"[red]Error: --lint requires 'on' or 'off'[/red]"
326+
)
327+
self.print_usage()
328+
sys.exit(1)
329+
lint_value = remaining_args[i + 1].lower()
330+
if lint_value not in ["on", "off"]:
331+
self.console.print("[red]Error: --lint must be 'on' or 'off'[/red]")
332+
self.print_usage()
333+
sys.exit(1)
334+
self.lint_enabled = lint_value == "on"
317335
elif arg == "--clean-build":
318336
self.clean_checksums()
319337
else:
@@ -1170,6 +1188,50 @@ def upload_config_library(self):
11701188
f"[green]Configuration library uploaded to s3://{self.bucket}/{self.prefix_and_version}/config_library[/green]"
11711189
)
11721190

1191+
def ui_changed(self):
1192+
"""Check if UI has changed based on zipfile hash, returns (changed, zipfile_path)"""
1193+
ui_hash = self.compute_ui_hash()
1194+
zipfile_name = f"src-{ui_hash[:16]}.zip"
1195+
zipfile_path = os.path.join(".aws-sam", zipfile_name)
1196+
1197+
existing_zipfiles = (
1198+
[
1199+
f
1200+
for f in os.listdir(".aws-sam")
1201+
if f.startswith("src-") and f.endswith(".zip")
1202+
]
1203+
if os.path.exists(".aws-sam")
1204+
else []
1205+
)
1206+
1207+
if zipfile_name not in existing_zipfiles:
1208+
# Remove old zipfiles
1209+
for old_zip in existing_zipfiles:
1210+
old_path = os.path.join(".aws-sam", old_zip)
1211+
if os.path.exists(old_path):
1212+
os.remove(old_path)
1213+
return True, zipfile_path
1214+
1215+
return not os.path.exists(zipfile_path), zipfile_path
1216+
1217+
def start_ui_validation_parallel(self):
1218+
"""Start UI validation in parallel if needed, returns (future, executor)"""
1219+
if not self.lint_enabled or not os.path.exists("src/ui"):
1220+
return None, None
1221+
1222+
changed, _ = self.ui_changed()
1223+
if not changed:
1224+
return None, None
1225+
1226+
import concurrent.futures
1227+
1228+
ui_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
1229+
ui_validation_future = ui_executor.submit(self.validate_ui_build)
1230+
self.console.print(
1231+
"[cyan]🔍 Starting UI validation in parallel with builds...[/cyan]"
1232+
)
1233+
return ui_validation_future, ui_executor
1234+
11731235
def compute_ui_hash(self):
11741236
"""Compute hash of UI folder contents"""
11751237
self.console.print("[cyan]Computing hash of ui folder contents[/cyan]")
@@ -1219,50 +1281,24 @@ def validate_ui_build(self):
12191281

12201282
def package_ui(self):
12211283
"""Package UI source code"""
1222-
ui_hash = self.compute_ui_hash()
1223-
zipfile_name = f"src-{ui_hash[:16]}.zip"
1224-
1225-
# Ensure .aws-sam directory exists
1226-
os.makedirs(".aws-sam", exist_ok=True)
1227-
1228-
# Check if we need to rebuild
1229-
existing_zipfiles = [
1230-
f
1231-
for f in os.listdir(".aws-sam")
1232-
if f.startswith("src-") and f.endswith(".zip")
1233-
]
1234-
1235-
if existing_zipfiles and existing_zipfiles[0] != zipfile_name:
1236-
self.console.print(
1237-
f"[yellow]WebUI zipfile name changed from {existing_zipfiles[0]} to {zipfile_name}, forcing rebuild[/yellow]"
1238-
)
1239-
# Remove old zipfile
1240-
for old_zip in existing_zipfiles:
1241-
old_path = os.path.join(".aws-sam", old_zip)
1242-
if os.path.exists(old_path):
1243-
os.remove(old_path)
1244-
1245-
zipfile_path = os.path.join(".aws-sam", zipfile_name)
1284+
_, zipfile_path = self.ui_changed()
12461285

12471286
if not os.path.exists(zipfile_path):
1248-
self.console.print("[bold cyan]PACKAGING src/ui[/bold cyan]")
1249-
self.console.print(f"[cyan]Zipping source to {zipfile_path}[/cyan]")
1250-
1287+
os.makedirs(".aws-sam", exist_ok=True)
12511288
with zipfile.ZipFile(zipfile_path, "w", zipfile.ZIP_DEFLATED) as zipf:
12521289
ui_dir = "src/ui"
12531290
exclude_dirs = {"node_modules", "build", ".aws-sam"}
12541291
for root, dirs, files in os.walk(ui_dir):
1255-
# Exclude specified directories from zipping
12561292
dirs[:] = [d for d in dirs if d not in exclude_dirs]
12571293
for file in files:
1258-
# Skip .env files
12591294
if file == ".env" or file.startswith(".env."):
12601295
continue
12611296
file_path = os.path.join(root, file)
12621297
arcname = os.path.relpath(file_path, ui_dir)
12631298
zipf.write(file_path, arcname)
12641299

12651300
# Check if file exists in S3 and upload if needed
1301+
zipfile_name = os.path.basename(zipfile_path)
12661302
s3_key = f"{self.prefix_and_version}/{zipfile_name}"
12671303
try:
12681304
self.s3_client.head_object(Bucket=self.bucket, Key=s3_key)
@@ -1329,10 +1365,6 @@ def build_main_template(self, webui_zipfile, components_needing_rebuild):
13291365
# Main template needs rebuilding, if any component needs rebuilding
13301366
if components_needing_rebuild:
13311367
self.console.print("[yellow]Main template needs rebuilding[/yellow]")
1332-
1333-
# Validate UI build before rebuilding
1334-
self.validate_ui_build()
1335-
13361368
# Validate Python syntax in src directory before building
13371369
if not self._validate_python_syntax("src"):
13381370
raise Exception("Python syntax validation failed")
@@ -1757,6 +1789,32 @@ def _validate_python_syntax(self, directory):
17571789
return False
17581790
return True
17591791

1792+
def _validate_python_linting(self):
1793+
"""Validate Python linting"""
1794+
if not self.lint_enabled:
1795+
return True
1796+
1797+
self.console.print("[cyan]🔍 Running Python linting...[/cyan]")
1798+
1799+
# Run ruff check (same as GitLab CI lint-cicd)
1800+
result = subprocess.run(["ruff", "check"], capture_output=True, text=True)
1801+
if result.returncode != 0:
1802+
self.console.print("[red]❌ Ruff linting failed![/red]")
1803+
self.console.print(result.stdout, style="red", markup=False)
1804+
return False
1805+
1806+
# Run ruff format check (same as GitLab CI lint-cicd)
1807+
result = subprocess.run(
1808+
["ruff", "format", "--check"], capture_output=True, text=True
1809+
)
1810+
if result.returncode != 0:
1811+
self.console.print("[red]❌ Code formatting check failed![/red]")
1812+
self.console.print(result.stdout, style="red", markup=False)
1813+
return False
1814+
1815+
self.console.print("[green]✅ Python linting passed[/green]")
1816+
return True
1817+
17601818
def build_lib_package(self):
17611819
"""Build lib package with syntax validation"""
17621820
try:
@@ -1926,12 +1984,19 @@ def run(self, args):
19261984
# Check prerequisites
19271985
self.check_prerequisites()
19281986

1987+
# Validate Python linting if enabled
1988+
if not self._validate_python_linting():
1989+
raise Exception("Python linting validation failed")
1990+
19291991
# Set up S3 bucket
19301992
self.setup_artifacts_bucket()
19311993

19321994
# Perform smart rebuild detection and cache management
19331995
components_needing_rebuild = self.smart_rebuild_detection()
19341996

1997+
# Start UI validation early in parallel
1998+
ui_validation_future, ui_executor = self.start_ui_validation_parallel()
1999+
19352000
# clear component cache
19362001
for comp_info in components_needing_rebuild:
19372002
if comp_info["component"] != "lib": # lib doesnt have sam build
@@ -2004,7 +2069,24 @@ def run(self, args):
20042069
# Upload configuration library
20052070
self.upload_config_library()
20062071

2007-
# Package UI
2072+
# Wait for UI validation to complete if it was started
2073+
if ui_validation_future:
2074+
try:
2075+
self.console.print(
2076+
"[cyan]⏳ Waiting for UI validation to complete...[/cyan]"
2077+
)
2078+
ui_validation_future.result()
2079+
self.console.print(
2080+
"[green]✅ UI validation completed successfully[/green]"
2081+
)
2082+
except Exception as e:
2083+
self.console.print("[red]❌ UI validation failed:[/red]")
2084+
self.console.print(str(e), style="red", markup=False)
2085+
sys.exit(1)
2086+
finally:
2087+
ui_executor.shutdown(wait=True)
2088+
2089+
# Package UI and start validation in parallel if needed
20082090
webui_zipfile = self.package_ui()
20092091

20102092
# Build main template

0 commit comments

Comments
 (0)