fix: Address high-severity bandit issues

This commit is contained in:
DSS
2025-12-11 07:13:06 -03:00
parent bcb4475744
commit 5b2a328dd1
167 changed files with 7051 additions and 7168 deletions

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""
DSS Storybook Generator
DSS Storybook Generator.
Generates Storybook stories from DSS tokens and component registry.
Hierarchy:
@@ -13,20 +14,17 @@ Usage: python3 scripts/generate-storybook.py [--output PATH] [--skin SKIN]
Default output: admin-ui/src/stories/
"""
import sys
import os
import json
import argparse
from pathlib import Path
import json
from datetime import datetime
from typing import Dict, Any, List
from pathlib import Path
DSS_ROOT = Path(__file__).parent.parent
DSS_DATA = DSS_ROOT / ".dss"
def load_json(path: Path) -> dict:
"""Load JSON file, return empty dict if not found"""
"""Load JSON file, return empty dict if not found."""
if not path.exists():
return {}
with open(path) as f:
@@ -34,12 +32,12 @@ def load_json(path: Path) -> dict:
def ensure_dir(path: Path):
"""Ensure directory exists"""
"""Ensure directory exists."""
path.mkdir(parents=True, exist_ok=True)
def generate_color_primitives_story(primitives: dict, output_dir: Path):
"""Generate story for color primitives (full Tailwind palette)"""
"""Generate story for color primitives (full Tailwind palette)."""
colors = primitives.get("color", {})
if not colors:
return
@@ -56,19 +54,23 @@ def generate_color_primitives_story(primitives: dict, output_dir: Path):
if name.startswith("_"):
continue
if isinstance(data, dict) and "value" in data:
border = "border: 1px solid #e5e7eb;" if data["value"] in ["#ffffff", "transparent"] else ""
base_swatches.append(f'''
border = (
"border: 1px solid #e5e7eb;" if data["value"] in ["#ffffff", "transparent"] else ""
)
base_swatches.append(
f"""
<div class="color-swatch">
<div class="swatch" style="background: {data['value']}; {border}"></div>
<div class="label">{name}</div>
<div class="value">{data['value']}</div>
</div>''')
</div>"""
)
if base_swatches:
base_section = f'''
base_section = f"""
<div class="color-section">
<h2>Base</h2>
<div class="swatch-row">{''.join(base_swatches)}</div>
</div>'''
</div>"""
# Neutral scales
neutrals = colors.get("neutral", {})
@@ -78,25 +80,31 @@ def generate_color_primitives_story(primitives: dict, output_dir: Path):
continue
if isinstance(scale, dict):
shades = []
for shade, data in sorted(scale.items(), key=lambda x: int(x[0]) if x[0].isdigit() else 0):
for shade, data in sorted(
scale.items(), key=lambda x: int(x[0]) if x[0].isdigit() else 0
):
if isinstance(data, dict) and "value" in data:
text_color = "#000" if int(shade) < 500 else "#fff"
shades.append(f'''
shades.append(
f"""
<div class="shade" style="background: {data['value']}; color: {text_color};">
<span>{shade}</span>
</div>''')
</div>"""
)
if shades:
neutral_palettes.append(f'''
neutral_palettes.append(
f"""
<div class="color-palette">
<h3>{scale_name}</h3>
<div class="shades">{''.join(shades)}</div>
</div>''')
</div>"""
)
if neutral_palettes:
neutral_section = f'''
neutral_section = f"""
<div class="color-section">
<h2>Neutral Scales</h2>
<div class="palette-grid">{''.join(neutral_palettes)}</div>
</div>'''
</div>"""
# Semantic scales
semantics = colors.get("semantic", {})
@@ -106,27 +114,33 @@ def generate_color_primitives_story(primitives: dict, output_dir: Path):
continue
if isinstance(scale, dict):
shades = []
for shade, data in sorted(scale.items(), key=lambda x: int(x[0]) if x[0].isdigit() else 0):
for shade, data in sorted(
scale.items(), key=lambda x: int(x[0]) if x[0].isdigit() else 0
):
if isinstance(data, dict) and "value" in data:
text_color = "#000" if int(shade) < 500 else "#fff"
shades.append(f'''
shades.append(
f"""
<div class="shade" style="background: {data['value']}; color: {text_color};">
<span>{shade}</span>
</div>''')
</div>"""
)
if shades:
semantic_palettes.append(f'''
semantic_palettes.append(
f"""
<div class="color-palette">
<h3>{scale_name}</h3>
<div class="shades">{''.join(shades)}</div>
</div>''')
</div>"""
)
if semantic_palettes:
semantic_section = f'''
semantic_section = f"""
<div class="color-section">
<h2>Semantic Scales</h2>
<div class="palette-grid">{''.join(semantic_palettes)}</div>
</div>'''
</div>"""
story = f'''/**
story = f"""/**
* Color Primitives - Foundation
* Full Tailwind color palette organized by category
* @generated {datetime.now().isoformat()}
@@ -169,26 +183,29 @@ export const AllColors = {{
</div>
`
}};
'''
"""
with open(output_dir / "ColorPrimitives.stories.js", "w") as f:
f.write(story)
print(f" [OK] ColorPrimitives.stories.js")
print(" [OK] ColorPrimitives.stories.js")
def generate_spacing_story(primitives: dict, output_dir: Path):
"""Generate story for spacing primitives"""
"""Generate story for spacing primitives."""
spacing = primitives.get("spacing", {})
if not spacing:
return
items = []
for name, data in sorted(spacing.items(), key=lambda x: float(x[0]) if x[0].replace('.', '').isdigit() else -1):
for name, data in sorted(
spacing.items(), key=lambda x: float(x[0]) if x[0].replace(".", "").isdigit() else -1
):
if name.startswith("_"):
continue
if isinstance(data, dict) and "value" in data:
comment = data.get("_comment", "")
items.append(f'''
items.append(
f"""
<div class="spacing-item">
<div class="bar" style="width: {data['value']};"></div>
<div class="info">
@@ -196,9 +213,10 @@ def generate_spacing_story(primitives: dict, output_dir: Path):
<span class="value">{data['value']}</span>
<span class="comment">{comment}</span>
</div>
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* Spacing Primitives - Foundation
* @generated {datetime.now().isoformat()}
*/
@@ -232,15 +250,15 @@ export const SpacingScale = {{
</div>
`
}};
'''
"""
with open(output_dir / "Spacing.stories.js", "w") as f:
f.write(story)
print(f" [OK] Spacing.stories.js")
print(" [OK] Spacing.stories.js")
def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path):
"""Generate story for typography tokens"""
"""Generate story for typography tokens."""
font = primitives.get("font", {})
typography = resolved.get("typography", {})
@@ -251,7 +269,8 @@ def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path
if name.startswith("_"):
continue
if isinstance(data, dict) and "value" in data:
family_samples.append(f'''
family_samples.append(
f"""
<div class="font-sample">
<div class="sample-text" style="font-family: {data['value']};">
The quick brown fox jumps over the lazy dog
@@ -260,21 +279,29 @@ def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path
<span class="name">{name}</span>
<span class="value">{data['value'][:40]}...</span>
</div>
</div>''')
</div>"""
)
# Font sizes
sizes = font.get("size", {})
size_samples = []
for name, data in sorted(sizes.items(), key=lambda x: float(x[0].replace('xl', '').replace('x', '')) if x[0].replace('xl', '').replace('x', '').replace('.', '').isdigit() else 0):
for name, data in sorted(
sizes.items(),
key=lambda x: float(x[0].replace("xl", "").replace("x", ""))
if x[0].replace("xl", "").replace("x", "").replace(".", "").isdigit()
else 0,
):
if name.startswith("_"):
continue
if isinstance(data, dict) and "value" in data:
size_samples.append(f'''
size_samples.append(
f"""
<div class="size-sample">
<span class="text" style="font-size: {data['value']};">Aa</span>
<span class="name">{name}</span>
<span class="value">{data['value']}</span>
</div>''')
</div>"""
)
# Font weights
weights = font.get("weight", {})
@@ -283,12 +310,14 @@ def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path
if name.startswith("_"):
continue
if isinstance(data, dict) and "value" in data:
weight_samples.append(f'''
weight_samples.append(
f"""
<div class="weight-sample">
<span class="text" style="font-weight: {data['value']};">Aa</span>
<span class="name">{name}</span>
<span class="value">{data['value']}</span>
</div>''')
</div>"""
)
# Typography styles from resolved tokens
style_samples = []
@@ -299,7 +328,8 @@ def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path
font_weight = props.get("font-weight", {}).get("value", 400)
line_height = props.get("line-height", {}).get("value", "1.5")
style_samples.append(f'''
style_samples.append(
f"""
<div class="style-sample">
<div class="text" style="font-family: {font_family}; font-size: {font_size}; font-weight: {font_weight}; line-height: {line_height};">
The quick brown fox
@@ -308,9 +338,10 @@ def generate_typography_story(primitives: dict, resolved: dict, output_dir: Path
<span class="name">{name}</span>
<span class="props">{font_size} / {font_weight}</span>
</div>
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* Typography - Foundation
* Font families, sizes, weights, and composed styles
* @generated {datetime.now().isoformat()}
@@ -394,15 +425,15 @@ export const TextStyles = {{
</div>
`
}};
'''
"""
with open(output_dir / "Typography.stories.js", "w") as f:
f.write(story)
print(f" [OK] Typography.stories.js")
print(" [OK] Typography.stories.js")
def generate_shadows_story(primitives: dict, resolved: dict, output_dir: Path):
"""Generate story for shadow tokens"""
"""Generate story for shadow tokens."""
shadows = primitives.get("shadow", {})
effects = resolved.get("effect", {})
@@ -420,14 +451,16 @@ def generate_shadows_story(primitives: dict, resolved: dict, output_dir: Path):
items = []
for name, value in all_shadows.items():
items.append(f'''
items.append(
f"""
<div class="shadow-card">
<div class="box" style="box-shadow: {value};"></div>
<div class="name">{name}</div>
<div class="value">{value[:50]}{"..." if len(value) > 50 else ""}</div>
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* Shadows - Foundation
* Box shadow scale
* @generated {datetime.now().isoformat()}
@@ -460,15 +493,15 @@ export const AllShadows = {{
</div>
`
}};
'''
"""
with open(output_dir / "Shadows.stories.js", "w") as f:
f.write(story)
print(f" [OK] Shadows.stories.js")
print(" [OK] Shadows.stories.js")
def generate_radius_story(primitives: dict, output_dir: Path):
"""Generate story for border radius tokens"""
"""Generate story for border radius tokens."""
radius = primitives.get("radius", {})
if not radius:
return
@@ -479,15 +512,17 @@ def generate_radius_story(primitives: dict, output_dir: Path):
continue
if isinstance(data, dict) and "value" in data:
comment = data.get("_comment", "")
items.append(f'''
items.append(
f"""
<div class="radius-item">
<div class="box" style="border-radius: {data['value']};"></div>
<div class="name">{name}</div>
<div class="value">{data['value']}</div>
<div class="comment">{comment}</div>
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* Border Radius - Foundation
* @generated {datetime.now().isoformat()}
*/
@@ -520,15 +555,15 @@ export const RadiusScale = {{
</div>
`
}};
'''
"""
with open(output_dir / "Radius.stories.js", "w") as f:
f.write(story)
print(f" [OK] Radius.stories.js")
print(" [OK] Radius.stories.js")
def generate_semantic_colors_story(resolved: dict, output_dir: Path):
"""Generate story for semantic color tokens"""
"""Generate story for semantic color tokens."""
colors = resolved.get("color", {})
if not colors:
return
@@ -541,20 +576,20 @@ def generate_semantic_colors_story(resolved: dict, output_dir: Path):
"Accent": [],
"Muted": [],
"Destructive": [],
"Other": []
"Other": [],
}
for name, data in colors.items():
if isinstance(data, dict) and "value" in data:
value = data["value"]
comment = data.get("comment", "")
card = f'''
card = f"""
<div class="token-card">
<div class="swatch" style="background: {value};"></div>
<div class="name">{name}</div>
<div class="value">{value}</div>
<div class="comment">{comment}</div>
</div>'''
</div>"""
if any(x in name for x in ["background", "foreground", "card", "popover"]):
groups["Surface"].append(card)
@@ -574,13 +609,15 @@ def generate_semantic_colors_story(resolved: dict, output_dir: Path):
sections = []
for group_name, cards in groups.items():
if cards:
sections.append(f'''
sections.append(
f"""
<div class="token-group">
<h3>{group_name}</h3>
<div class="token-row">{''.join(cards)}</div>
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* Semantic Colors - Design Tokens
* Resolved color tokens for light theme
* @generated {datetime.now().isoformat()}
@@ -616,15 +653,15 @@ export const LightTheme = {{
</div>
`
}};
'''
"""
with open(output_dir / "SemanticColors.stories.js", "w") as f:
f.write(story)
print(f" [OK] SemanticColors.stories.js")
print(" [OK] SemanticColors.stories.js")
def generate_component_stories(registry: dict, output_dir: Path):
"""Generate stories for shadcn components from registry"""
"""Generate stories for shadcn components from registry."""
components = registry.get("components", {})
categories = registry.get("categories", {})
@@ -659,7 +696,8 @@ def generate_component_stories(registry: dict, output_dir: Path):
radix_badge = f'<span class="radix-badge">{radix}</span>' if radix else ""
deps_text = ", ".join(deps[:3]) if deps else ""
component_cards.append(f'''
component_cards.append(
f"""
<div class="component-card">
<div class="card-header">
<h3>{name}</h3>
@@ -668,9 +706,10 @@ def generate_component_stories(registry: dict, output_dir: Path):
<p class="description">{desc}</p>
<div class="variants">{variant_badges}</div>
{f'<div class="deps">deps: {deps_text}</div>' if deps_text else ''}
</div>''')
</div>"""
)
story = f'''/**
story = f"""/**
* {cat_name}
* {cat_desc}
* @generated {datetime.now().isoformat()}
@@ -735,7 +774,7 @@ export const Overview = {{
</div>
`
}};
'''
"""
# Create safe filename
filename = f"Components{cat_name.replace(' ', '')}.stories.js"
@@ -745,8 +784,8 @@ export const Overview = {{
def generate_overview_story(output_dir: Path):
"""Generate overview/introduction story"""
story = f'''/**
"""Generate overview/introduction story."""
story = f"""/**
* Design System Overview
* @generated {datetime.now().isoformat()}
*/
@@ -840,17 +879,18 @@ background: var(--color-background);</code></pre>
</div>
`
}};
'''
"""
with open(output_dir / "Overview.stories.js", "w") as f:
f.write(story)
print(f" [OK] Overview.stories.js")
print(" [OK] Overview.stories.js")
def main():
parser = argparse.ArgumentParser(description="Generate Storybook stories from DSS tokens")
parser.add_argument("--output", default="admin-ui/src/stories",
help="Output directory for stories")
parser.add_argument(
"--output", default="admin-ui/src/stories", help="Output directory for stories"
)
parser.add_argument("--skin", default="shadcn", help="Skin to use")
args = parser.parse_args()
@@ -875,7 +915,9 @@ def main():
print(f"[INFO] Core primitives: {len(primitives)} categories")
print(f"[INFO] Skin: {args.skin}")
print(f"[INFO] Resolved tokens: {sum(len(v) if isinstance(v, dict) else 0 for v in resolved.values())} tokens")
print(
f"[INFO] Resolved tokens: {sum(len(v) if isinstance(v, dict) else 0 for v in resolved.values())} tokens"
)
print(f"[INFO] Component registry: {len(registry.get('components', {}))} components")
print("")
@@ -898,7 +940,7 @@ def main():
print("")
story_count = len(list(output_dir.glob("*.stories.js")))
print(f"[OK] Generated {story_count} story files")
print(f"[OK] Run: cd admin-ui && npm run storybook")
print("[OK] Run: cd admin-ui && npm run storybook")
if __name__ == "__main__":