📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-07-03 16:04:10 +00:00
parent 2bf579321a
commit b40381458b
482 changed files with 8324 additions and 2007 deletions
@@ -29,61 +29,47 @@ function extractColorsFromMarkdown(content) {
accent: { name: 'accent', shades: {} }
};
// Extract primary color name and hex from Quick Reference table
const quickRefMatch = content.match(/Primary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
if (quickRefMatch) {
colors.primary.name = quickRefMatch[2].toLowerCase().replace(/\s+/g, '-');
colors.primary.base = `#${quickRefMatch[1]}`;
// Match a "| Label | #hex |" markdown table row. Bold around the label
// (**Label**) is optional, so this handles both the bundled starter template
// ("| Primary Blue | #2563EB |") and bolded variants.
const rowRe = /\|\s*\*{0,2}([^*|]+?)\*{0,2}\s*\|\s*#([A-Fa-f0-9]{6})\b/g;
// 1) Quick Reference table — hex only, no parenthesized name required.
const quickRef = {
primary: /Primary Color\s*\|\s*#([A-Fa-f0-9]{6})/i,
secondary: /Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})/i,
accent: /Accent Color\s*\|\s*#([A-Fa-f0-9]{6})/i
};
for (const key of Object.keys(quickRef)) {
const m = content.match(quickRef[key]);
if (m) colors[key].base = `#${m[1]}`;
}
const secondaryMatch = content.match(/Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
if (secondaryMatch) {
colors.secondary.name = secondaryMatch[2].toLowerCase().replace(/\s+/g, '-');
colors.secondary.base = `#${secondaryMatch[1]}`;
}
const accentMatch = content.match(/Accent Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/);
if (accentMatch) {
colors.accent.name = accentMatch[2].toLowerCase().replace(/\s+/g, '-');
colors.accent.base = `#${accentMatch[1]}`;
}
// Extract all shades from Primary Colors table
const primarySection = content.match(/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
if (primarySection) {
const hexMatches = primarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
for (const match of hexMatches) {
const name = match[1].trim().toLowerCase();
const hex = `#${match[2]}`;
if (name.includes('dark')) colors.primary.dark = hex;
else if (name.includes('light')) colors.primary.light = hex;
else colors.primary.base = hex;
// 2) Dedicated "### <Role> Colors" tables — assign base/dark/light by the
// row label keyword.
const assignFromSection = (heading, target) => {
const section = content.match(new RegExp(`### ${heading}[\\s\\S]*?(?=\\n###|$)`, 'i'));
if (!section) return;
for (const m of section[0].matchAll(rowRe)) {
const label = m[1].trim().toLowerCase();
const hex = `#${m[2]}`;
if (label.includes('dark')) target.dark = hex;
else if (label.includes('light')) target.light = hex;
else if (!target.base) target.base = hex;
}
}
};
assignFromSection('Primary Colors', colors.primary);
assignFromSection('Secondary Colors', colors.secondary);
assignFromSection('Accent Colors', colors.accent);
// Extract secondary shades
const secondarySection = content.match(/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
if (secondarySection) {
const hexMatches = secondarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
for (const match of hexMatches) {
const name = match[1].trim().toLowerCase();
const hex = `#${match[2]}`;
if (name.includes('dark')) colors.secondary.dark = hex;
else if (name.includes('light')) colors.secondary.light = hex;
else colors.secondary.base = hex;
}
}
// Extract accent shades
const accentSection = content.match(/### Accent Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i);
if (accentSection) {
const hexMatches = accentSection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g);
for (const match of hexMatches) {
const name = match[1].trim().toLowerCase();
const hex = `#${match[2]}`;
if (name.includes('dark')) colors.accent.dark = hex;
else if (name.includes('light')) colors.accent.light = hex;
else colors.accent.base = hex;
// 3) Fallback: an accent swatch may live in another table (the starter
// lists "Accent Green" under Secondary Colors).
if (!colors.accent.base) {
for (const m of content.matchAll(rowRe)) {
if (m[1].trim().toLowerCase().includes('accent')) {
colors.accent.base = `#${m[2]}`;
break;
}
}
}
@@ -113,6 +99,7 @@ function generateColorScale(baseHex, darkHex, lightHex) {
* Adjust hex color brightness
*/
function adjustBrightness(hex, percent) {
if (typeof hex !== 'string') return '#000000';
const num = parseInt(hex.replace('#', ''), 16);
const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));
const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent)));
@@ -129,29 +116,24 @@ function updateDesignTokens(tokens, colors) {
tokens.brand = brandName;
// Update primitive colors with new names
const primitiveColors = tokens.primitive?.color || {};
tokens.primitive = tokens.primitive || {};
const primitiveColors = tokens.primitive.color || {};
// Remove old color keys, add new ones
delete primitiveColors.coral;
delete primitiveColors.purple;
delete primitiveColors.mint;
// Add new named colors
primitiveColors[colors.primary.name] = generateColorScale(
colors.primary.base,
colors.primary.dark,
colors.primary.light
);
primitiveColors[colors.secondary.name] = generateColorScale(
colors.secondary.base,
colors.secondary.dark,
colors.secondary.light
);
primitiveColors[colors.accent.name] = generateColorScale(
colors.accent.base,
colors.accent.dark,
colors.accent.light
);
// Add new named colors. Skip any role with no base hex rather than crashing
// on an unexpected guidelines format.
for (const role of ['primary', 'secondary', 'accent']) {
const c = colors[role];
if (!c.base) {
console.warn(`⚠️ No base hex found for ${role} color — skipping its token scale.`);
continue;
}
primitiveColors[c.name] = generateColorScale(c.base, c.dark, c.light);
}
tokens.primitive.color = primitiveColors;
@@ -191,7 +173,7 @@ function updateDesignTokens(tokens, colors) {
}
// Update component references (button uses primary color with opacity)
if (tokens.component?.button?.secondary) {
if (tokens.component?.button?.secondary && colors.primary.base) {
const primaryBase = colors.primary.base;
tokens.component.button.secondary['bg-hover'] = {
"$value": `${primaryBase}1A`,
@@ -0,0 +1,52 @@
"""Regression test for sync-brand-to-tokens.cjs.
The color parser required a parenthesized name in the Quick Reference row
(`#2563EB (name)`) and a bolded label in the color tables (`**Primary Blue**`),
neither of which the bundled starter template uses. As a result the base hex
came back `undefined` and `adjustBrightness(undefined)` threw a TypeError —
i.e. the script crashed on its own documented happy path. This test runs the
sync against the bundled starter template and asserts it completes and writes
the expected base colors. It is pytest-based so the existing pytest CI runs it.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
SCRIPTS = Path(__file__).resolve().parent.parent
SCRIPT = SCRIPTS / "sync-brand-to-tokens.cjs"
BRAND_STARTER = SCRIPTS.parent / "templates" / "brand-guidelines-starter.md"
TOKENS_STARTER = (
SCRIPTS.parent.parent / "design-system" / "templates" / "design-tokens-starter.json"
)
def test_sync_parses_bundled_starter_template(tmp_path):
node = shutil.which("node")
if not node:
pytest.skip("node not available")
(tmp_path / "docs").mkdir()
(tmp_path / "assets").mkdir()
shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
result = subprocess.run(
[node, str(SCRIPT)],
cwd=tmp_path,
capture_output=True,
text=True,
)
# Must not crash (the bug raised an unhandled TypeError).
assert "TypeError" not in result.stderr, result.stderr
assert result.returncode == 0, result.stderr + result.stdout
tokens = json.loads((tmp_path / "assets" / "design-tokens.json").read_text())
primitive = tokens["primitive"]["color"]
assert primitive["primary"]["500"]["$value"] == "#2563EB"
assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
assert primitive["accent"]["500"]["$value"] == "#10B981"
@@ -0,0 +1,48 @@
"""Regression tests for validate-tokens.cjs.
The validator used to skip any line containing ``var(--`` outright, so a
hardcoded value sharing a line with a token reference (extremely common in
real CSS, and universal in minified CSS where everything is one line) went
undetected. These tests drive the CLI via ``node`` and assert it flags such
cases. They are pytest-based so the repository's existing pytest CI runs them.
"""
import shutil
import subprocess
from pathlib import Path
import pytest
SCRIPT = Path(__file__).resolve().parent.parent / "validate-tokens.cjs"
def _run(tmp_path: Path, css: str) -> subprocess.CompletedProcess:
node = shutil.which("node")
if not node:
pytest.skip("node not available")
(tmp_path / "sample.css").write_text(css)
return subprocess.run(
[node, str(SCRIPT), "--dir", str(tmp_path)],
capture_output=True,
text=True,
)
def test_flags_hardcoded_hex_sharing_line_with_token(tmp_path):
"""A hardcoded hex on the same line as a var() token is still a violation."""
result = _run(
tmp_path,
".btn { background: #FF6B6B; color: var(--color-primary); }\n",
)
assert "#FF6B6B" in result.stdout, result.stdout
assert result.returncode == 1
def test_token_only_line_reports_no_violation(tmp_path):
"""A line that references only tokens produces no false positives."""
result = _run(
tmp_path,
".btn { background: var(--color-bg); color: var(--color-primary); }\n",
)
assert "No token violations" in result.stdout, result.stdout
assert result.returncode == 0
@@ -137,11 +137,6 @@ function scanFile(filePath) {
return;
}
// Skip lines that already use CSS variables
if (line.includes('var(--')) {
return;
}
for (const [name, pattern] of Object.entries(patterns)) {
const matches = line.match(pattern.regex);
if (matches) {
@@ -213,7 +213,7 @@ class TailwindConfigGenerator:
return f"""import type {{ Config }} from 'tailwindcss'
const config: Config = {{
{self._indent_json(config_json, 1)}
{self._indent_json(config_json, 1)},
plugins: [{plugins_str}],
}}
@@ -230,7 +230,7 @@ export default config
return f"""/** @type {{import('tailwindcss').Config}} */
module.exports = {{
{self._indent_json(config_json, 1)}
{self._indent_json(config_json, 1)},
plugins: [{plugins_str}],
}}
"""
@@ -1,5 +1,7 @@
"""Tests for tailwind_config_gen.py"""
import shutil
import subprocess
from pathlib import Path
import pytest
@@ -334,3 +336,59 @@ class TestTailwindConfigGenerator:
assert "module.exports" in content
assert "primary" in content
assert "@tailwindcss/forms" in content
def _strip_to_object(config_str: str) -> str:
"""Reduce a generated TS/JS config to a bare assignable object so it can be
handed to `node --check` without a TypeScript loader."""
lines = []
for line in config_str.splitlines():
if line.startswith("import type"):
continue
if line.strip() == "export default config":
continue
line = line.replace("const config: Config =", "const config =")
line = line.replace("module.exports =", "const config =")
lines.append(line)
return "\n".join(lines)
class TestGeneratedConfigIsValidJs:
"""Regression guard for the missing-comma bug between the ``theme`` block and
``plugins`` that produced syntactically invalid config files. The data-shape
tests above all passed while the emitted string was unparseable, so these
tests validate the serialized output itself."""
@pytest.mark.parametrize("typescript", [True, False])
def test_property_before_plugins_is_comma_terminated(self, typescript):
"""The property preceding ``plugins`` must end with a comma (pure-Python
check, so the regression is caught even where node is unavailable)."""
generator = TailwindConfigGenerator(typescript=typescript)
generator.add_colors({"brand": "#6366F1"})
generator.add_breakpoints({"3xl": "1920px"})
config = generator.generate_config_string()
assert "}\n plugins:" not in config, "missing comma before plugins"
assert "},\n plugins:" in config
@pytest.mark.parametrize("typescript", [True, False])
def test_node_check_parses_generated_config(self, typescript, tmp_path):
"""The emitted config parses as valid JS via ``node --check``."""
node = shutil.which("node")
if not node:
pytest.skip("node not available")
generator = TailwindConfigGenerator(typescript=typescript)
generator.add_colors({"brand": "#6366F1", "accent": "#10B981"})
generator.add_fonts({"sans": ["Inter"]})
generator.add_breakpoints({"3xl": "1920px"})
generator.add_plugins(["tailwindcss-animate"])
snippet = _strip_to_object(generator.generate_config_string())
path = tmp_path / "config.cjs"
path.write_text(snippet)
result = subprocess.run(
[node, "--check", str(path)], capture_output=True, text=True
)
assert result.returncode == 0, result.stderr