cardbuddy 0.23.0 → 0.25.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 +2 -2
- package/dist/init.js +9 -2
- package/dist/preview-editor/assets/{index-C7N5n_qW.js → index-DL4ZCJCA.js} +77 -76
- package/dist/preview-editor/assets/{index-7RzNd7tf.css → index-MWKPP9nd.css} +1 -1
- package/dist/preview-editor/index.html +2 -2
- package/dist/skills-upgrade.js +59 -0
- package/dist/vendored/server/core/browser.js +7 -4
- package/dist/vendored/server/core/cardCompile.js +202 -0
- package/dist/vendored/server/core/flameCompile/client.js +466 -0
- package/dist/vendored/server/core/flameCompile/clientElements.js +372 -0
- package/dist/vendored/server/core/flameCompile/common.js +253 -0
- package/dist/vendored/server/core/flameCompile/h5.js +326 -0
- package/dist/vendored/server/core/flameCompile/index.js +126 -0
- package/dist/vendored/server/core/flameCompile/model.js +162 -0
- package/dist/vendored/server/core/index.js +6 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
- package/dist/vendored/skills/mbank-design-gen/tools/screenshot.mjs +15 -7
- package/dist/vendored/skills/platform-card-sync/scripts/api_batch_sync.mjs +12 -4
- package/dist/vendored/skills/platform-session-navigator/scripts/package-lock.json +1 -16
- package/dist/vendored/skills/platform-session-navigator/scripts/package.json +2 -2
- package/dist/vendored/skills/platform-session-navigator/scripts/platform_login.mjs +12 -1
- package/dist/vendored/skills/platform-session-navigator/scripts/session-manager.mjs +1 -1
- package/package.json +2 -4
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16-Cg6Nrosk.png" />
|
|
9
9
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32-B_1744sp.png" />
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-BQcES9QK.png" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DL4ZCJCA.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-MWKPP9nd.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="app"></div>
|
package/dist/skills-upgrade.js
CHANGED
|
@@ -18,12 +18,16 @@
|
|
|
18
18
|
* 交互环境外(非 TTY)无法确认时,必须带 --force 才能升级,否则明确拒绝并提示(不再
|
|
19
19
|
* 静默跳过、假装升级成功)。
|
|
20
20
|
* 标志:--force/-y 跳过确认直接覆盖;--prune 额外删除 ORPHAN;--dry-run 只报告不落盘。
|
|
21
|
+
* 依赖检查:升级后探测 skills/ 内 package.json 声明的依赖能否从工程解析,缺失的
|
|
22
|
+
* 显式给出 `npm install <pkg>` 补装指引(升级不自动装依赖——是否动工程 node_modules
|
|
23
|
+
* 由用户决定;解析探测与脚本运行时的 Node 解析链一致)。
|
|
21
24
|
* 同步后写 skills/.synced-from = 当前装包版本,供下次 drift 检测与启动提示。
|
|
22
25
|
*/
|
|
23
26
|
import fs from 'node:fs'
|
|
24
27
|
import path from 'node:path'
|
|
25
28
|
import readline from 'node:readline/promises'
|
|
26
29
|
import { fileURLToPath } from 'node:url'
|
|
30
|
+
import { createRequire } from 'node:module'
|
|
27
31
|
import chalk from 'chalk'
|
|
28
32
|
import { loadConfig } from './config.js'
|
|
29
33
|
|
|
@@ -75,6 +79,38 @@ function walkFiles(dir, base = dir, out = []) {
|
|
|
75
79
|
return out
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
/**
|
|
83
|
+
* 收集 skills/ 内各 package.json 声明的依赖中「从其所在目录无法解析」的包名。
|
|
84
|
+
* 技能脚本运行时按 Node 规则从脚本位置向上找 node_modules(先 skill 自身,再工程根),
|
|
85
|
+
* 这里用 createRequire 从该 package.json 所在目录模拟同一解析链;解析不到的即缺口——
|
|
86
|
+
* 升级只拷文件不装依赖,缺口必须显式提示用户补装(0.83.x 事故的教训:
|
|
87
|
+
* playwright 曾是 optional 依赖静默缺失,脚本运行时才报 Cannot find package)。
|
|
88
|
+
* @param {string} destSkills 工程内 skills/ 目录
|
|
89
|
+
* @returns {string[]} 形如 "playwright-core(platform-session-navigator/scripts/package.json)"
|
|
90
|
+
*/
|
|
91
|
+
function collectMissingSkillDeps(destSkills) {
|
|
92
|
+
const missing = []
|
|
93
|
+
for (const rel of walkFiles(destSkills)) {
|
|
94
|
+
if (path.posix.basename(rel) !== 'package.json') continue
|
|
95
|
+
const pkgPath = path.join(destSkills, rel)
|
|
96
|
+
let deps
|
|
97
|
+
try {
|
|
98
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
99
|
+
deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
100
|
+
} catch {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
for (const name of Object.keys(deps)) {
|
|
104
|
+
try {
|
|
105
|
+
createRequire(path.join(pkgPath, 'index.js')).resolve(name)
|
|
106
|
+
} catch {
|
|
107
|
+
missing.push(`${name}(${rel})`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return missing
|
|
112
|
+
}
|
|
113
|
+
|
|
78
114
|
/**
|
|
79
115
|
* 比对源/目标技能树(内容比对)。
|
|
80
116
|
* @returns {{ added:string[], same:string[], modified:string[], orphan:string[] }}
|
|
@@ -153,6 +189,25 @@ export async function runSkillsUpgrade({
|
|
|
153
189
|
const warn = (m) => say(chalk.yellow(`• ${m}`))
|
|
154
190
|
const dim = (m) => say(chalk.dim(`• ${m}`))
|
|
155
191
|
|
|
192
|
+
// 依赖在位检查(升级只同步文件、不装依赖,但必须把缺口显式暴露出来):
|
|
193
|
+
// 逐个解析 skills/ 内 package.json 声明的依赖,解析不到的给出补装命令。
|
|
194
|
+
const reportDeps = () => {
|
|
195
|
+
let missing
|
|
196
|
+
try {
|
|
197
|
+
missing = collectMissingSkillDeps(destSkills)
|
|
198
|
+
} catch {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
if (!missing.length) {
|
|
202
|
+
dim('技能依赖在位:skills/ 内 package.json 声明的依赖均可从工程解析')
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
const names = [...new Set(missing.map((m) => m.slice(0, m.indexOf('('))))]
|
|
206
|
+
warn(`⚠ 检测到 ${missing.length} 个技能依赖无法解析(新版本技能可能带来新依赖):`)
|
|
207
|
+
for (const m of missing) say(chalk.yellow(` ${m}`))
|
|
208
|
+
say(chalk.yellow(` 请在工程根执行:npm install ${names.join(' ')}`))
|
|
209
|
+
}
|
|
210
|
+
|
|
156
211
|
if (dryRun) say(chalk.yellow('(dry-run:只报告将要做的事,不落盘)'))
|
|
157
212
|
|
|
158
213
|
if (!src || !fs.existsSync(src)) {
|
|
@@ -169,6 +224,7 @@ export async function runSkillsUpgrade({
|
|
|
169
224
|
|
|
170
225
|
if (!diff.added.length && !diff.modified.length && !diff.orphan.length && fs.existsSync(destSkills)) {
|
|
171
226
|
dim(`skills 已是最新(cardbuddy@${version},无新增 / 修改 / 孤儿)`)
|
|
227
|
+
reportDeps()
|
|
172
228
|
return 0
|
|
173
229
|
}
|
|
174
230
|
|
|
@@ -240,6 +296,9 @@ export async function runSkillsUpgrade({
|
|
|
240
296
|
}
|
|
241
297
|
}
|
|
242
298
|
|
|
299
|
+
// 依赖在位检查(dry-run 也报,作为升级后的预期状态)
|
|
300
|
+
reportDeps()
|
|
301
|
+
|
|
243
302
|
// 汇总
|
|
244
303
|
log('')
|
|
245
304
|
say(chalk.green(dryRun ? 'dry-run 完成(未落盘)' : 'skills 升级完成'))
|
|
@@ -35,8 +35,11 @@ let chromiumMod = null
|
|
|
35
35
|
|
|
36
36
|
/** 取(惰性启动的)单例浏览器;启动失败会把 browserPromise 复位并抛出 */
|
|
37
37
|
async function getBrowser() {
|
|
38
|
-
// 动态 import playwright:避免其进入 vite.config
|
|
39
|
-
|
|
38
|
+
// 动态 import playwright-core:避免其进入 vite.config 加载图(见文件头注释)。
|
|
39
|
+
// 用 playwright-core 而非 playwright:浏览器走系统 Chrome(channel: 'chrome'),
|
|
40
|
+
// 只需 JS API;playwright 的 postinstall 浏览器下载作为 optional 依赖失败时会被
|
|
41
|
+
// npm 静默跳过,导致 vendored 发布态运行时报 Cannot find package(0.83.x 事故)。
|
|
42
|
+
if (!chromiumMod) chromiumMod = await import('playwright-core')
|
|
40
43
|
if (!browserPromise) {
|
|
41
44
|
browserPromise = chromiumMod.chromium
|
|
42
45
|
.launch({ channel: 'chrome', headless: true })
|
|
@@ -98,9 +101,9 @@ function serialize(task) {
|
|
|
98
101
|
|
|
99
102
|
/**
|
|
100
103
|
* 打开卡片渲染页(chrome=off 纯净模式)并等待首帧稳定。
|
|
101
|
-
* @param {import('playwright').BrowserContext} context
|
|
104
|
+
* @param {import('playwright-core').BrowserContext} context
|
|
102
105
|
* @param {string} id 卡片 id
|
|
103
|
-
* @returns {Promise<{ page: import('playwright').Page, screen: import('playwright').ElementHandle | null }>}
|
|
106
|
+
* @returns {Promise<{ page: import('playwright-core').Page, screen: import('playwright-core').ElementHandle | null }>}
|
|
104
107
|
*/
|
|
105
108
|
async function openCardScreen(context, id) {
|
|
106
109
|
const page = await context.newPage()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 卡片编译端点:把本地 schema.json 编译成两套可投放产物
|
|
3
|
+
*
|
|
4
|
+
* POST /__compile-cards body { cardIds: string[], targetDir?: string }
|
|
5
|
+
* → { ok, outDir, cards: [{ id, name, ok, version, templates, nodes, clientBytes, h5Bytes, warnings, error }] }
|
|
6
|
+
*
|
|
7
|
+
* 产物布局(客户端 / H5 分两个目录,同属一个父目录):
|
|
8
|
+
* <targetDir>/ ← 默认 <工程根>/flame-build
|
|
9
|
+
* client/<cardCode>/<templateCode>/index.{json,css,databinding,data,js,style}
|
|
10
|
+
* h5/<cardCode>.js
|
|
11
|
+
* manifest.json ← 目录内产物清单(累计)+ 本次编译批次(lastBatch)
|
|
12
|
+
*
|
|
13
|
+
* 编译逻辑本身在 flameCompile/ 下(1:1 迁移自 flame-release-mange 的 card/exporter),
|
|
14
|
+
* 本文件只负责「读盘 → 编译 → 落盘 → 汇报」,不含任何渲染规则。
|
|
15
|
+
*
|
|
16
|
+
* 覆盖语义:产物是构建缓存,允许直接覆盖——
|
|
17
|
+
* - 逐文件 writeFileSync 截断重写,同卡重复编译结果字节一致;
|
|
18
|
+
* - 写之前先删该卡的整个模板目录,模板数量变少(如删掉列表)时不会残留 _sub_/_item;
|
|
19
|
+
* - 编译失败不删旧产物(先编译成功再落盘),上一次的好产物不会被半途失败搞坏;
|
|
20
|
+
* - manifest 同理不丢信息:按 id 并入已有记录(本次为准),只保留磁盘上仍有产物的卡。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'node:fs'
|
|
24
|
+
import path from 'node:path'
|
|
25
|
+
import { compileCard } from './flameCompile/index.js'
|
|
26
|
+
import { log } from './logger.js'
|
|
27
|
+
|
|
28
|
+
/** 单次编译上限(与导出的 500 同口径) */
|
|
29
|
+
const MAX_COMPILE_CARDS = 500
|
|
30
|
+
|
|
31
|
+
/** 卡片 id 白名单:只允许字母数字下划线中划线,杜绝目录穿越 */
|
|
32
|
+
const CARD_ID_RE = /^[A-Za-z0-9_-]+$/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 读已有 manifest 的 cards 段。缺失 / 损坏 / 结构不对一律当空数组 ——
|
|
36
|
+
* 一份旧清单坏掉不该让整批编译失败(它是构建产物,不是输入)。
|
|
37
|
+
*/
|
|
38
|
+
function readPrevManifestCards(manifestPath) {
|
|
39
|
+
try {
|
|
40
|
+
const mf = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
41
|
+
return Array.isArray(mf.cards) ? mf.cards : []
|
|
42
|
+
} catch {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createCompileMiddleware({ root }) {
|
|
48
|
+
const readJsonBody = (req, res, cb) => {
|
|
49
|
+
let body = ''
|
|
50
|
+
req.setEncoding('utf8')
|
|
51
|
+
req.on('data', (c) => {
|
|
52
|
+
body += c
|
|
53
|
+
if (body.length > 1024 * 1024) req.destroy()
|
|
54
|
+
})
|
|
55
|
+
req.on('end', () => {
|
|
56
|
+
try {
|
|
57
|
+
cb(JSON.parse(body || '{}'))
|
|
58
|
+
} catch (e) {
|
|
59
|
+
res.statusCode = 400
|
|
60
|
+
res.setHeader('Content-Type', 'application/json')
|
|
61
|
+
res.end(JSON.stringify({ ok: false, error: 'invalid json', message: String(e.message) }))
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const sendJson = (res, status, obj) => {
|
|
67
|
+
res.statusCode = status
|
|
68
|
+
res.setHeader('Content-Type', 'application/json')
|
|
69
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
70
|
+
res.end(JSON.stringify(obj))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return function compileMiddleware(req, res, next) {
|
|
74
|
+
const pathname = decodeURIComponent((req.url || '').split('?')[0])
|
|
75
|
+
const method = (req.method || 'GET').toUpperCase()
|
|
76
|
+
if (pathname !== '/__compile-cards' || method !== 'POST') return next()
|
|
77
|
+
|
|
78
|
+
readJsonBody(req, res, (body) => {
|
|
79
|
+
const rawIds = Array.isArray(body.cardIds)
|
|
80
|
+
? body.cardIds.filter((x) => typeof x === 'string')
|
|
81
|
+
: []
|
|
82
|
+
// 去重:同一张卡在同一批里传两次没有意义(第二次会把第一次刚写的产物删掉再写一遍),只留首个
|
|
83
|
+
const ids = [...new Set(rawIds)]
|
|
84
|
+
if (!ids.length) return sendJson(res, 400, { ok: false, error: 'cardIds 不能为空' })
|
|
85
|
+
if (ids.length > MAX_COMPILE_CARDS) {
|
|
86
|
+
return sendJson(res, 400, { ok: false, error: `单次最多编译 ${MAX_COMPILE_CARDS} 张卡片` })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const bad = ids.filter((id) => !CARD_ID_RE.test(id))
|
|
90
|
+
if (bad.length) return sendJson(res, 400, { ok: false, error: `非法卡片 id: ${bad.join(', ')}` })
|
|
91
|
+
|
|
92
|
+
// 目标目录:默认工程根下 flame-build;自定义时限制在工程根内,避免误写到系统目录
|
|
93
|
+
const outDir = body.targetDir
|
|
94
|
+
? path.resolve(root, String(body.targetDir))
|
|
95
|
+
: path.join(root, 'flame-build')
|
|
96
|
+
const rootResolved = path.resolve(root)
|
|
97
|
+
if (!outDir.startsWith(rootResolved + path.sep)) {
|
|
98
|
+
return sendJson(res, 400, { ok: false, error: 'targetDir 必须位于工程目录内' })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const clientDir = path.join(outDir, 'client')
|
|
102
|
+
const h5Dir = path.join(outDir, 'h5')
|
|
103
|
+
fs.mkdirSync(clientDir, { recursive: true })
|
|
104
|
+
fs.mkdirSync(h5Dir, { recursive: true })
|
|
105
|
+
|
|
106
|
+
const results = []
|
|
107
|
+
const manifestCards = []
|
|
108
|
+
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
const schemaPath = path.join(root, 'src', 'cards', id, 'schema.json')
|
|
111
|
+
if (!fs.existsSync(schemaPath)) {
|
|
112
|
+
results.push({ id, ok: false, error: 'schema.json 不存在' })
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
|
|
118
|
+
const compiled = compileCard(schema, { cardCode: id })
|
|
119
|
+
|
|
120
|
+
// 清掉该卡的旧产物(模板数量可能变化)
|
|
121
|
+
const cardClientDir = path.join(clientDir, id)
|
|
122
|
+
if (fs.existsSync(cardClientDir)) fs.rmSync(cardClientDir, { recursive: true, force: true })
|
|
123
|
+
|
|
124
|
+
for (const template of compiled.client) {
|
|
125
|
+
const dir = path.join(cardClientDir, template.code)
|
|
126
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
127
|
+
for (const [file, content] of Object.entries(template.files)) {
|
|
128
|
+
// index.style 是 SM4 密文(Buffer),必须按字节原样落盘;
|
|
129
|
+
// 其余是 utf8 文本,显式声明编码便于排查乱码。
|
|
130
|
+
if (Buffer.isBuffer(content)) fs.writeFileSync(path.join(dir, file), content)
|
|
131
|
+
else fs.writeFileSync(path.join(dir, file), content, 'utf8')
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fs.writeFileSync(path.join(h5Dir, `${id}.js`), compiled.h5, 'utf8')
|
|
136
|
+
|
|
137
|
+
results.push({
|
|
138
|
+
id,
|
|
139
|
+
ok: true,
|
|
140
|
+
version: compiled.version,
|
|
141
|
+
templates: compiled.client.length,
|
|
142
|
+
nodes: compiled.stats.nodes,
|
|
143
|
+
clientBytes: compiled.stats.clientBytes,
|
|
144
|
+
h5Bytes: compiled.stats.h5Bytes,
|
|
145
|
+
warnings: compiled.warnings,
|
|
146
|
+
})
|
|
147
|
+
manifestCards.push({
|
|
148
|
+
id,
|
|
149
|
+
name: schema.name || '',
|
|
150
|
+
version: compiled.version,
|
|
151
|
+
templates: compiled.client.map((t) => t.code),
|
|
152
|
+
nodes: compiled.stats.nodes,
|
|
153
|
+
clientBytes: compiled.stats.clientBytes,
|
|
154
|
+
h5Bytes: compiled.stats.h5Bytes,
|
|
155
|
+
warnings: compiled.warnings,
|
|
156
|
+
})
|
|
157
|
+
} catch (e) {
|
|
158
|
+
log.warn(`[compile] ${id} 编译失败: ${e.message}`)
|
|
159
|
+
results.push({ id, ok: false, error: String(e.message || e) })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const okCount = results.filter((r) => r.ok).length
|
|
164
|
+
// manifest 描述的是「这个目录里有什么」,因此跨批次累计:按 id 合并,本次记录覆盖同 id 旧记录。
|
|
165
|
+
// 只按本次批次重写会得到一份自相矛盾的清单——磁盘上 10 张卡,manifest 里只有最后勾的 2 张。
|
|
166
|
+
// 旧记录还要过一道「磁盘上仍有产物」的存在性检查:换过 targetDir、手工删过文件时它会失真。
|
|
167
|
+
const manifestPath = path.join(outDir, 'manifest.json')
|
|
168
|
+
const merged = new Map()
|
|
169
|
+
for (const c of readPrevManifestCards(manifestPath)) {
|
|
170
|
+
if (!c || typeof c.id !== 'string' || !c.id) continue
|
|
171
|
+
const hasArtifacts =
|
|
172
|
+
fs.existsSync(path.join(clientDir, c.id)) || fs.existsSync(path.join(h5Dir, `${c.id}.js`))
|
|
173
|
+
if (hasArtifacts) merged.set(c.id, c)
|
|
174
|
+
}
|
|
175
|
+
for (const c of manifestCards) merged.set(c.id, c)
|
|
176
|
+
const manifest = {
|
|
177
|
+
generatedAt: new Date().toISOString(),
|
|
178
|
+
outDir,
|
|
179
|
+
// 目录内卡片总数(累计),不是本批张数
|
|
180
|
+
count: merged.size,
|
|
181
|
+
cards: [...merged.values()].sort((a, b) => String(a.id).localeCompare(String(b.id))),
|
|
182
|
+
// 本次请求的批次结果,便于回溯「这一次又编了什么」
|
|
183
|
+
lastBatch: {
|
|
184
|
+
requested: ids.length,
|
|
185
|
+
ok: okCount,
|
|
186
|
+
failed: ids.length - okCount,
|
|
187
|
+
ids,
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
191
|
+
|
|
192
|
+
log.info(`[compile] 编译完成 ${okCount}/${ids.length} → ${outDir}`)
|
|
193
|
+
return sendJson(res, 200, {
|
|
194
|
+
ok: okCount > 0,
|
|
195
|
+
outDir,
|
|
196
|
+
clientDir,
|
|
197
|
+
h5Dir,
|
|
198
|
+
cards: results,
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
}
|