Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a9f6985
fix: Automatically serialize `page_content` to JSON string to resolve…
hexqi Mar 5, 2026
6acc71e
feat: Implement a pluggable store abstraction with a new file-based s…
hexqi Mar 5, 2026
bf0be96
fix: Resolve P0 issues in store adapter implementation
hexqi Mar 5, 2026
b9ce9d3
feat: Implement file-based data storage for mock server entities and …
hexqi Mar 5, 2026
5f2ce0a
fix(mockServer): filter nedb metadata in export-db-to-file
hexqi Mar 5, 2026
8ee2d7a
chore: add base file data and dev:file cmd
hexqi Mar 18, 2026
c5eed6b
feat: add dsl generator skill
hexqi Mar 18, 2026
e382cb3
fix: windows start error
hexqi Mar 23, 2026
0b02ba8
Merge remote-tracking branch 'hexqi/develop' into feat/file-as-database
hexqi Mar 23, 2026
0a4b25a
Merge remote-tracking branch 'origin/develop' into feat/file-as-database
hexqi Jun 22, 2026
641be77
chore: move skill from .claude to .agents directory
hexqi Jun 22, 2026
3570654
fix: review
hexqi Jun 22, 2026
7d01778
fix: add deep equality check and update query matching
hexqi Jun 22, 2026
2b2a78b
fix: skill symlink
hexqi Jun 25, 2026
b59add3
docs: add guide for using Skill to generate pages
hexqi Jun 25, 2026
278aa63
fix: docs
hexqi Jun 25, 2026
0ba0b14
docs: update
hexqi Jun 25, 2026
4b260f9
chore: replace tracked symlink with per-platform link script
hexqi Jun 25, 2026
6eddb99
fix: update check script
hexqi Jun 25, 2026
42baf46
refactor: update python script to nodejs mjs
hexqi Jun 25, 2026
e39da95
fix: docs
hexqi Jun 25, 2026
cee2ccb
feat: update skill component props and event binding rules
hexqi Jun 25, 2026
d5c287c
fix: review
hexqi Jun 26, 2026
e0b935e
fix: review
hexqi Jun 29, 2026
1832605
fix(FileStore): add case sensitivity detection for file operations
hexqi Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
671 changes: 671 additions & 0 deletions .agents/skills/tinyengine-dsl-generator/SKILL.md
Comment thread
chilingling marked this conversation as resolved.

Large diffs are not rendered by default.

614 changes: 614 additions & 0 deletions .agents/skills/tinyengine-dsl-generator/references/components.md

Large diffs are not rendered by default.

1,141 changes: 1,141 additions & 0 deletions .agents/skills/tinyengine-dsl-generator/references/patterns.md

Large diffs are not rendered by default.

595 changes: 595 additions & 0 deletions .agents/skills/tinyengine-dsl-generator/references/protocol.md

Large diffs are not rendered by default.

252 changes: 252 additions & 0 deletions .agents/skills/tinyengine-dsl-generator/scripts/check_css.py
Comment thread
chilingling marked this conversation as resolved.
Outdated
Comment thread
hexqi marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""
TinyEngine CSS Syntax Checker

检查DSL中的CSS字段是否有语法错误。

支持三种模式:
1. basic: 基础语法检查(默认,无需额外依赖)
2. tinycss2: 使用 tinycss2 库(需要安装:pip install tinycss2)
3. postcss: 使用 postcss 命令行工具(需要安装:npm install -g postcss)

用法:
python3 check_css.py <dsl-file> [mode]
python3 check_css.py <dsl-file> postcss # 使用postcss检查
python3 check_css.py <dsl-file> tinycss2 # 使用tinycss2检查
"""

import json
import re
import subprocess
import sys
from typing import Dict, List, Any


class CSSChecker:
"""CSS语法检查器基类"""

def __init__(self, dsl_data: Dict[str, Any]):
self.dsl = dsl_data
self.errors = []
self.warnings = []

def check(self) -> bool:
"""检查CSS语法"""
page_content = self.dsl.get('page_content', self.dsl)
css_string = page_content.get('css', '')

if not css_string:
self.warnings.append("No CSS field found")
return True

return self._check_css(css_string)

def _check_css(self, css: str) -> bool:
"""子类实现具体的检查逻辑"""
raise NotImplementedError

def report(self) -> str:
"""生成报告"""
lines = []
if self.errors:
lines.append("❌ CSS Errors:")
for error in self.errors:
lines.append(f" - {error}")
if self.warnings:
lines.append("⚠️ CSS Warnings:")
for warning in self.warnings:
lines.append(f" - {warning}")
if not self.errors and not self.warnings:
lines.append("✅ CSS check passed!")
return "\n".join(lines)


class BasicCSSChecker(CSSChecker):
"""基础CSS语法检查器(无需额外依赖)"""

def _check_css(self, css: str) -> bool:
"""基础检查:括号匹配、基本语法"""
# 检查括号匹配
stack = []
i = 0
while i < len(css):
char = css[i]
if char in '{':
stack.append((char, i))
elif char in '}':
if not stack or stack[-1][0] != '{':
self.errors.append(f"Unmatched '}}' at position {i}")
return False
stack.pop()
elif char in '(':
stack.append((char, i))
elif char in ')':
if not stack or stack[-1][0] != '(':
self.errors.append(f"Unmatched ')' at position {i}")
return False
stack.pop()
i += 1

if stack:
for char, pos in stack:
self.errors.append(f"Unclosed '{char}' at position {pos}")
return False

# 移除注释进行检查
css_no_comments = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL)

# 检查是否有CSS规则
if '{' not in css_no_comments:
self.warnings.append("CSS may not contain any rules")

# 检查分号使用
rules = re.findall(r'\{([^}]*)\}', css_no_comments)
for rule in rules:
properties = rule.split(';')
for prop in properties[:-1]: # 最后一个可能为空
prop = prop.strip()
if prop and ':' not in prop:
self.warnings.append(f"Property without colon: {prop[:50]}")

return len(self.errors) == 0


class TinyCSS2Checker(CSSChecker):
"""使用tinycss2库的CSS检查器"""

def _check_css(self, css: str) -> bool:
try:
import tinycss2
except ImportError:
self.errors.append(
"tinycss2 not installed. Install with: pip install tinycss2"
)
return False

# 使用tinycss2解析CSS
try:
# 解析CSS规则列表
rules = tinycss2.parse_stylesheet(css, skip_comments=False)

# tinycss2会抛出解析错误
for rule in rules:
if rule.type == 'error':
self.errors.append(f"Parse error: {rule.message}")

return len(self.errors) == 0

except Exception as e:
self.errors.append(f"CSS check failed unexpectedly: {e}")
return False


class PostCSSChecker(CSSChecker):
"""使用postcss命令行工具的CSS检查器"""

def _check_css(self, css: str) -> bool:
# 检查postcss是否可用
try:
result = subprocess.run(
['postcss', '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode != 0:
self.errors.append("postcss command failed. Install with: npm install -g postcss")
return False
except FileNotFoundError:
self.errors.append("postcss not found. Install with: npm install -g postcss")
return False
except subprocess.TimeoutExpired:
self.errors.append("postcss command timed out")
return False

# 将CSS写入临时文件
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.css', delete=False) as f:
f.write(css)
temp_file = f.name

try:
# 使用postcss解析CSS
result = subprocess.run(
['postcss', temp_file],
capture_output=True,
text=True,
timeout=10
)

# postcss的错误输出
if result.stderr:
# 过滤掉警告(如<css input>:3:3: Yellow color)
errors = []
warnings = []
for line in result.stderr.strip().split('\n'):
if line:
if any(w in line.lower() for w in ['warning', 'yellow']):
warnings.append(line)
else:
errors.append(line)

self.errors.extend(errors)
self.warnings.extend(warnings)

return len(self.errors) == 0

except subprocess.TimeoutExpired:
self.errors.append("postcss command timed out")
return False
finally:
import os
try:
os.unlink(temp_file)
except OSError:
# FileNotFoundError (subclass of OSError) is expected if the temp
# file was never created or already removed; other OS-level
# cleanup failures are non-fatal here.
pass


def main():
"""主函数"""
if len(sys.argv) < 2:
print("Usage: check_css.py <dsl-file> [mode]")
print(" mode: basic (default), tinycss2, postcss")
sys.exit(1)

file_path = sys.argv[1]
mode = sys.argv[2] if len(sys.argv) > 2 else 'basic'

# 读取DSL文件
try:
with open(file_path, 'r', encoding='utf-8') as f:
dsl_data = json.load(f)
except json.JSONDecodeError as e:
print(f"❌ Invalid JSON: {e}")
sys.exit(1)
except FileNotFoundError:
print(f"❌ File not found: {file_path}")
sys.exit(1)

# 选择检查器
checkers = {
'basic': BasicCSSChecker,
'tinycss2': TinyCSS2Checker,
'postcss': PostCSSChecker
}

checker_class = checkers.get(mode)
if not checker_class:
print(f"❌ Unknown mode: {mode}")
print(f"Available modes: {', '.join(checkers.keys())}")
sys.exit(1)

checker = checker_class(dsl_data)
is_valid = checker.check()
print(checker.report())
sys.exit(0 if is_valid else 1)


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""
TinyEngine Event Binding Checker

检查DSL文件中的事件绑定是否正确使用JSExpression引用方法,
而不是在value中直接写函数定义。
"""

import json
import sys
from typing import Dict, List, Any


class EventBindingChecker:
"""事件绑定检查器"""

def __init__(self, dsl_data: Dict[str, Any]):
self.dsl = dsl_data
self.errors = []
self.warnings = []

def check(self) -> bool:
"""检查所有事件绑定"""
# 从page_content或直接检查
page_content = self.dsl.get('page_content', self.dsl)

self._check_node(page_content)
return len(self.errors) == 0

def _check_node(self, node: Any) -> None:
"""递归检查节点"""
if isinstance(node, dict):
# 检查当前节点的事件绑定
self._check_event_bindings(node)

# 递归检查子节点
if 'children' in node:
for child in node['children']:
self._check_node(child)

elif isinstance(node, list):
for item in node:
self._check_node(item)

def _check_event_bindings(self, node: Dict[str, Any]) -> None:
"""检查单个节点的事件绑定(含 props 内的事件绑定)"""
component = node.get('componentName', 'unknown')

# 两处都需要校验,避免漏检 props 内的事件。
self._check_event_holder(node, component)
props = node.get('props')
if isinstance(props, dict):
self._check_event_holder(props, component)

def _check_event_holder(self, holder: Dict[str, Any], component: str) -> None:
"""检查某个属性容器(节点本身或其 props)内的事件绑定"""
# 检查所有可能的事件属性
event_keys = [
'onClick', 'onChange', 'onKeyup', 'onKeyDown', 'onKeyPress',
'onFocus', 'onBlur', 'onSubmit', 'onInput', 'onTabClick',
'onCurrentChange', 'onSizeChange', 'onCheckChange',
'onNodeClick', 'onRowClick', 'onCellClick'
]

for key in event_keys:
if key in holder:
value = holder[key]
if isinstance(value, dict):
self._check_event_value(component, key, value)

# 也检查以'on'开头的属性
for key, value in holder.items():
if key.startswith('on') and key not in event_keys:
if isinstance(value, dict):
self._check_event_value(component, key, value)

def _check_event_value(self, component: str, event_key: str, value: Dict[str, Any]) -> None:
"""检查事件值"""
value_type = value.get('type')
value_content = value.get('value', '')

# 错误1: 使用JSFunction类型进行事件绑定
if value_type == 'JSFunction':
self.errors.append(
f"{component}.{event_key}: 使用了JSFunction类型,应该使用JSExpression引用methods中的方法"
)

# 错误2: JSExpression的value中包含函数定义
if value_type == 'JSExpression' and value_content.startswith('function'):
self.errors.append(
f"{component}.{event_key}: JSExpression的value中包含函数定义 '{value_content[:30]}...',"
f"应该引用方法如 'this.methodName'"
)

def report(self) -> str:
"""生成报告"""
lines = []
if self.errors:
lines.append("❌ 发现事件绑定错误:")
for error in self.errors:
lines.append(f" - {error}")
if self.warnings:
lines.append("⚠️ 警告:")
for warning in self.warnings:
lines.append(f" - {warning}")
if not self.errors and not self.warnings:
lines.append("✅ 所有事件绑定检查通过!")
return "\n".join(lines)


def main():
"""主函数"""
if len(sys.argv) < 2:
print("Usage: check_event_bindings.py <dsl-file>")
sys.exit(1)

file_path = sys.argv[1]

try:
with open(file_path, 'r', encoding='utf-8') as f:
dsl_data = json.load(f)
except json.JSONDecodeError as e:
print(f"❌ Invalid JSON: {e}")
sys.exit(1)
except FileNotFoundError:
print(f"❌ File not found: {file_path}")
sys.exit(1)

checker = EventBindingChecker(dsl_data)
is_valid = checker.check()
print(checker.report())
sys.exit(0 if is_valid else 1)


if __name__ == '__main__':
main()
Loading
Loading