-
Notifications
You must be signed in to change notification settings - Fork 474
feat: suport mockserver file as database and add dsl-genenrator skill #1792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hexqi
wants to merge
13
commits into
opentiny:develop
Choose a base branch
from
hexqi:feat/file-as-database
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a9f6985
fix: Automatically serialize `page_content` to JSON string to resolve…
hexqi 6acc71e
feat: Implement a pluggable store abstraction with a new file-based s…
hexqi bf0be96
fix: Resolve P0 issues in store adapter implementation
hexqi b9ce9d3
feat: Implement file-based data storage for mock server entities and …
hexqi 5f2ce0a
fix(mockServer): filter nedb metadata in export-db-to-file
hexqi 8ee2d7a
chore: add base file data and dev:file cmd
hexqi c5eed6b
feat: add dsl generator skill
hexqi e382cb3
fix: windows start error
hexqi 0b02ba8
Merge remote-tracking branch 'hexqi/develop' into feat/file-as-database
hexqi 0a4b25a
Merge remote-tracking branch 'origin/develop' into feat/file-as-database
hexqi 641be77
chore: move skill from .claude to .agents directory
hexqi 3570654
fix: review
hexqi 7d01778
fix: add deep equality check and update query matching
hexqi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
614 changes: 614 additions & 0 deletions
614
.agents/skills/tinyengine-dsl-generator/references/components.md
Large diffs are not rendered by default.
Oops, something went wrong.
1,141 changes: 1,141 additions & 0 deletions
1,141
.agents/skills/tinyengine-dsl-generator/references/patterns.md
Large diffs are not rendered by default.
Oops, something went wrong.
595 changes: 595 additions & 0 deletions
595
.agents/skills/tinyengine-dsl-generator/references/protocol.md
Large diffs are not rendered by default.
Oops, something went wrong.
252 changes: 252 additions & 0 deletions
252
.agents/skills/tinyengine-dsl-generator/scripts/check_css.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
|
hexqi marked this conversation as resolved.
|
||
|
|
||
| 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() | ||
136 changes: 136 additions & 0 deletions
136
.agents/skills/tinyengine-dsl-generator/scripts/check_event_bindings.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
|
|
||
|
hexqi marked this conversation as resolved.
|
||
| 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() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.