cardbuddy 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/dist/cli.js +17 -6
- package/dist/config.js +20 -2
- package/dist/portGuard.js +293 -0
- package/dist/preview-editor/assets/index-B4VZgNSK.css +1 -0
- package/dist/preview-editor/assets/index-jl4YJzKv.js +180 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/server-core/_vendor/finmall-env.mjs +4 -4
- package/dist/server-core/ai-chat.js +3710 -0
- package/dist/server-core/assetProxy.js +3 -22
- package/dist/server-core/buildDataFromSchema.js +36 -4
- package/dist/server-core/cardFiles.js +95 -2
- package/dist/server-core/cardImage.js +14 -0
- package/dist/server-core/cardListCache.js +0 -17
- package/dist/server-core/cardSave.js +2 -0
- package/dist/server-core/cardValidate.js +3 -0
- package/dist/server-core/index.js +6 -5
- package/dist/server-core/logStream.js +1 -6
- package/dist/server-core/sliceUpload.js +8 -1
- package/dist/standalone.js +24 -8
- package/dist/vendored/App.vue +405 -125
- package/dist/vendored/api.js +4 -4
- package/dist/vendored/assets/svgIcons/icons.svg +28 -0
- package/dist/vendored/components/AiSettings.vue +392 -0
- package/dist/vendored/components/CardGallery.vue +51 -5
- package/dist/vendored/components/ChatComposer.vue +413 -0
- package/dist/vendored/components/ChatMessage.vue +383 -0
- package/dist/vendored/components/ChatPanel.vue +937 -0
- package/dist/vendored/components/ComponentPalette.vue +1 -1
- package/dist/vendored/components/DebugPanel.vue +732 -90
- package/dist/vendored/components/Dropdown.vue +4 -2
- package/dist/vendored/components/MarkdownBody.vue +64 -0
- package/dist/vendored/components/NewCardDialog.vue +132 -0
- package/dist/vendored/components/NumberStepper.vue +125 -0
- package/dist/vendored/components/ProgressSlider.vue +9 -1
- package/dist/vendored/components/RemotePreview.vue +1 -1
- package/dist/vendored/components/StructureTree.vue +2 -2
- package/dist/vendored/main.js +1 -0
- package/dist/vendored/runtime/adapter.js +1 -1
- package/dist/vendored/runtime/ai-api.js +165 -0
- package/dist/vendored/runtime/clipboard-node.js +0 -5
- package/dist/vendored/runtime/markdown.js +140 -0
- package/dist/vendored/skills/.manifest.json +1101 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +249 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +678 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/derive_data_json.py +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +105 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +624 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +900 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/slice_upload.mjs +699 -0
- package/dist/vendored/skills/shared/finmall-config.mjs +124 -0
- package/dist/vendored/styles/ai-chat.css +1850 -0
- package/dist/vendored/styles/app.css +725 -213
- package/dist/vendored/styles/card-gallery.css +48 -10
- package/dist/vendored/styles/card-select.css +13 -6
- package/dist/vendored/styles/dropdown.css +1 -2
- package/dist/vendored/styles/json-tree.css +3 -3
- package/dist/vendored/styles/node-inspector.css +13 -289
- package/dist/vendored/styles/setters.css +48 -49
- package/dist/vendored/styles/structure-tree.css +6 -11
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-B9MOriNv.js +0 -80
- package/dist/preview-editor/assets/index-CwD14gm3.css +0 -1
- package/dist/vendored/assets/logo.png +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
autofix_schema.py — 机械修复「契约级」高频错误,把「校验-修复」多轮压成 1 轮。
|
|
4
|
+
|
|
5
|
+
只修可由代码确定性决策的 ERROR 类问题(不臆造语义):
|
|
6
|
+
1. itemValueFrom == "1" 但缺 itemDefaultValue → 按值类型补兜底(字符串 '' / 数字 0 / 布尔 False / 数组 [] / 对象 {})。
|
|
7
|
+
2. 重复 componentId → 重编号为全局连续唯一 comp_N。
|
|
8
|
+
3. 缺失 data.json 或 key 与 inputParams 漂移 → 由 inputParams.defaultValue 重新派生并写回。
|
|
9
|
+
|
|
10
|
+
不处理需要语义判断的 WARNING(未知样式子键、flex 枚举等)——那些留给人工/卡片作者。
|
|
11
|
+
|
|
12
|
+
用法:
|
|
13
|
+
python3 autofix_schema.py <schema.json> # 修复并写回
|
|
14
|
+
python3 autofix_schema.py <schema.json> --check # 只报告,不写回
|
|
15
|
+
python3 autofix_schema.py <schema.json> --no-data # 跳过 data.json 派生
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
26
|
+
import validate_schema as V # 复用硬契约校验
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _fallback_for(value):
|
|
30
|
+
if isinstance(value, bool):
|
|
31
|
+
return False
|
|
32
|
+
if isinstance(value, (int, float)):
|
|
33
|
+
return 0
|
|
34
|
+
if isinstance(value, str):
|
|
35
|
+
return ''
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return []
|
|
38
|
+
if isinstance(value, dict):
|
|
39
|
+
return {}
|
|
40
|
+
return ''
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _children(node):
|
|
44
|
+
kids = []
|
|
45
|
+
if isinstance(node.get('components'), list):
|
|
46
|
+
kids.extend(node['components'])
|
|
47
|
+
for s in node.get('slots', []) or []:
|
|
48
|
+
if isinstance(s, dict):
|
|
49
|
+
kids.extend(s.get('components', []) or [])
|
|
50
|
+
return kids
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def walk_fix_value_from(node, fixed):
|
|
54
|
+
"""机械补 itemValueFrom:缺失/空串 → '0'(常量,最安全默认)。
|
|
55
|
+
|
|
56
|
+
运行时 property.js onPreview 只在 itemValueFrom 为 '0'/'1'/'2' 时才把属性写进预览
|
|
57
|
+
产物;缺失则整条属性被静默丢弃(背景/内边距/gap/对齐失效)。手写样式极易漏写,
|
|
58
|
+
故在此统一兜底为 '0'(按 itemValue 原值当常量用)。变量绑定('1')本就必带,不会走到这里。
|
|
59
|
+
"""
|
|
60
|
+
changed = False
|
|
61
|
+
for grp in node.get('style', {}).get('properties', []) or []:
|
|
62
|
+
# 顶层样式分组对象本身也必须带 itemValueFrom(如 font/background 分组),
|
|
63
|
+
# 否则 validate 仍报 ERROR(之前 autofix 只补子项、漏补分组本体,font 分组
|
|
64
|
+
# 缺失时卡片字体/背景整组被运行时静默丢弃)。
|
|
65
|
+
if not grp.get('itemValueFrom'):
|
|
66
|
+
grp['itemValueFrom'] = '0'
|
|
67
|
+
fixed.append(f"补 itemValueFrom='0': style.{grp.get('itemKey')}(分组)")
|
|
68
|
+
changed = True
|
|
69
|
+
for ch in grp.get('itemChildren', {}).get('properties', []) or []:
|
|
70
|
+
if not ch.get('itemValueFrom'):
|
|
71
|
+
ch['itemValueFrom'] = '0'
|
|
72
|
+
fixed.append(f"补 itemValueFrom='0': style.{grp.get('itemKey')}.{ch.get('itemKey')}")
|
|
73
|
+
changed = True
|
|
74
|
+
for pr in node.get('properties', []) or []:
|
|
75
|
+
if not pr.get('itemValueFrom'):
|
|
76
|
+
pr['itemValueFrom'] = '0'
|
|
77
|
+
fixed.append(f"补 itemValueFrom='0': {node.get('className')}/{pr.get('itemKey')}")
|
|
78
|
+
changed = True
|
|
79
|
+
for d in node.get('directives', []) or []:
|
|
80
|
+
if not d.get('itemValueFrom'):
|
|
81
|
+
d['itemValueFrom'] = '0'
|
|
82
|
+
fixed.append(f"补 itemValueFrom='0': directive.{d.get('itemKey')}")
|
|
83
|
+
changed = True
|
|
84
|
+
for child in _children(node):
|
|
85
|
+
if walk_fix_value_from(child, fixed):
|
|
86
|
+
changed = True
|
|
87
|
+
return changed
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def walk_fix_defaults(node, fixed):
|
|
91
|
+
changed = False
|
|
92
|
+
for prop in node.get('properties', []) or []:
|
|
93
|
+
if prop.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in prop:
|
|
94
|
+
prop['itemDefaultValue'] = _fallback_for(prop.get('itemValue'))
|
|
95
|
+
fixed.append(f"补 itemDefaultValue: {node.get('className')}/{prop.get('itemKey')}")
|
|
96
|
+
changed = True
|
|
97
|
+
for grp in node.get('style', {}).get('properties', []) or []:
|
|
98
|
+
if grp.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in grp:
|
|
99
|
+
grp['itemDefaultValue'] = _fallback_for(grp.get('itemValue'))
|
|
100
|
+
fixed.append(f"补 style itemDefaultValue: {grp.get('itemKey')}(分组)")
|
|
101
|
+
changed = True
|
|
102
|
+
for child in grp.get('itemChildren', {}).get('properties', []) or []:
|
|
103
|
+
if child.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in child:
|
|
104
|
+
child['itemDefaultValue'] = _fallback_for(child.get('itemValue'))
|
|
105
|
+
fixed.append(f"补 style itemDefaultValue: {node.get('className')}/{grp.get('itemKey')}/{child.get('itemKey')}")
|
|
106
|
+
changed = True
|
|
107
|
+
for d in node.get('directives', []) or []:
|
|
108
|
+
if d.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in d:
|
|
109
|
+
d['itemDefaultValue'] = _fallback_for(d.get('itemValue'))
|
|
110
|
+
fixed.append(f"补 directive itemDefaultValue: {node.get('className')}/{d.get('itemKey')}")
|
|
111
|
+
changed = True
|
|
112
|
+
for child in _children(node):
|
|
113
|
+
if walk_fix_defaults(child, fixed):
|
|
114
|
+
changed = True
|
|
115
|
+
return changed
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def fix_duplicate_ids(node, fixed):
|
|
119
|
+
seen = set()
|
|
120
|
+
duplicated = False
|
|
121
|
+
|
|
122
|
+
def count(n):
|
|
123
|
+
nonlocal duplicated
|
|
124
|
+
if not isinstance(n, dict):
|
|
125
|
+
return
|
|
126
|
+
cid = n.get('componentId')
|
|
127
|
+
if cid and cid in seen:
|
|
128
|
+
duplicated = True
|
|
129
|
+
seen.add(cid)
|
|
130
|
+
for c in _children(n):
|
|
131
|
+
count(c)
|
|
132
|
+
count(node)
|
|
133
|
+
if not duplicated:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
seq = {'n': 0}
|
|
137
|
+
|
|
138
|
+
def renumber(n):
|
|
139
|
+
if not isinstance(n, dict):
|
|
140
|
+
return
|
|
141
|
+
seq['n'] += 1
|
|
142
|
+
n['componentId'] = f"comp_{seq['n']}"
|
|
143
|
+
for c in _children(n):
|
|
144
|
+
renumber(c)
|
|
145
|
+
renumber(node)
|
|
146
|
+
fixed.append("重编号全部 componentId 为连续唯一序列(修复重复)")
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def fix_data_json(schema, schema_dir, fixed, skip=False):
|
|
151
|
+
if skip:
|
|
152
|
+
return False
|
|
153
|
+
data_path = os.path.join(schema_dir, 'data.json')
|
|
154
|
+
params = schema.get('dictionary', {}).get('inputParams', [])
|
|
155
|
+
expected = {p['key']: p.get('defaultValue') for p in params if 'key' in p}
|
|
156
|
+
if not os.path.isfile(data_path):
|
|
157
|
+
with open(data_path, 'w', encoding='utf-8') as f:
|
|
158
|
+
json.dump(expected, f, ensure_ascii=False, indent=2)
|
|
159
|
+
f.write('\n')
|
|
160
|
+
fixed.append(f"生成缺失的 data.json({len(expected)} 个 key)")
|
|
161
|
+
return True
|
|
162
|
+
try:
|
|
163
|
+
data = json.load(open(data_path, encoding='utf-8'))
|
|
164
|
+
except Exception:
|
|
165
|
+
data = {}
|
|
166
|
+
drift = (set(expected) - set(data)) | (set(data) - set(expected))
|
|
167
|
+
if drift:
|
|
168
|
+
merged = dict(data)
|
|
169
|
+
for k, v in expected.items():
|
|
170
|
+
if k not in merged:
|
|
171
|
+
merged[k] = v
|
|
172
|
+
with open(data_path, 'w', encoding='utf-8') as f:
|
|
173
|
+
json.dump(merged, f, ensure_ascii=False, indent=2)
|
|
174
|
+
f.write('\n')
|
|
175
|
+
fixed.append(f"修正 data.json key 漂移({len(drift)} 个 key)")
|
|
176
|
+
return True
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _validate_obj(schema, check_data=True):
|
|
181
|
+
fd, tmp = tempfile.mkstemp(suffix='.json')
|
|
182
|
+
try:
|
|
183
|
+
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
184
|
+
json.dump(schema, f, ensure_ascii=False)
|
|
185
|
+
return V.validate_schema(tmp, check_data=check_data)
|
|
186
|
+
finally:
|
|
187
|
+
try:
|
|
188
|
+
os.remove(tmp)
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def autofix(path, check=False, no_data=False):
|
|
194
|
+
schema_dir = os.path.dirname(path)
|
|
195
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
196
|
+
schema = json.load(f)
|
|
197
|
+
fixed = []
|
|
198
|
+
walk_fix_value_from(schema.get('view', {}), fixed)
|
|
199
|
+
walk_fix_defaults(schema.get('view', {}), fixed)
|
|
200
|
+
fix_duplicate_ids(schema.get('view', {}), fixed)
|
|
201
|
+
fix_data_json(schema, schema_dir, fixed, skip=no_data)
|
|
202
|
+
|
|
203
|
+
if not check and fixed:
|
|
204
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
205
|
+
json.dump(schema, f, ensure_ascii=False, indent=2)
|
|
206
|
+
f.write('\n')
|
|
207
|
+
|
|
208
|
+
t0 = time.time()
|
|
209
|
+
ok, errors, warnings = _validate_obj(schema, check_data=not no_data)
|
|
210
|
+
duration = int((time.time() - t0) * 1000)
|
|
211
|
+
return {
|
|
212
|
+
'fixed': fixed,
|
|
213
|
+
'ok': ok,
|
|
214
|
+
'errors': errors,
|
|
215
|
+
'warnings': warnings,
|
|
216
|
+
'durationMs': duration,
|
|
217
|
+
'written': (not check and bool(fixed)),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main():
|
|
222
|
+
args = sys.argv[1:]
|
|
223
|
+
positional = [a for a in args if not a.startswith('--')]
|
|
224
|
+
flags = set(a for a in args if a.startswith('--'))
|
|
225
|
+
if not positional:
|
|
226
|
+
print("Usage: python3 autofix_schema.py <schema.json> [--check] [--no-data]")
|
|
227
|
+
sys.exit(1)
|
|
228
|
+
path = positional[0]
|
|
229
|
+
check = '--check' in flags
|
|
230
|
+
no_data = '--no-data' in flags
|
|
231
|
+
if not os.path.isfile(path):
|
|
232
|
+
print(f"[error] File not found: {path}")
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
|
|
235
|
+
res = autofix(path, check=check, no_data=no_data)
|
|
236
|
+
print(f"\n[autofix] {'--check 模式(未写回)' if check else '已尝试修复'}")
|
|
237
|
+
if res['fixed']:
|
|
238
|
+
for fx in res['fixed']:
|
|
239
|
+
print(f" [FIX] {fx}")
|
|
240
|
+
else:
|
|
241
|
+
print(" [FIX] 无机械错误可修复")
|
|
242
|
+
print(f"\n[result] ok={res['ok']} errors={len(res['errors'])} warnings={len(res['warnings'])} ({res['durationMs']}ms)")
|
|
243
|
+
for e in res['errors'][:20]:
|
|
244
|
+
print(f" [ERROR] {e}")
|
|
245
|
+
sys.exit(0 if res['ok'] else 1)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == '__main__':
|
|
249
|
+
main()
|