cardbuddy 0.24.0 → 0.25.1
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-BumNBo-n.js → index-B6NhNlBx.js} +16 -16
- package/dist/preview-editor/index.html +1 -1
- package/dist/skills-upgrade.js +59 -0
- package/dist/vendored/server/core/browser.js +7 -4
- package/dist/vendored/server/core/flameCompile/client.js +6 -4
- package/dist/vendored/server/core/flameCompile/common.js +47 -2
- package/dist/vendored/server/core/flameCompile/h5.js +30 -10
- 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,7 +8,7 @@
|
|
|
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-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-B6NhNlBx.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-MWKPP9nd.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
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()
|
|
@@ -238,17 +238,19 @@ function convertDirectives(component, template, warnings) {
|
|
|
238
238
|
const extend = bucket.extend && typeof bucket.extend === 'object' ? bucket.extend : {}
|
|
239
239
|
bucket.extend = extend
|
|
240
240
|
|
|
241
|
+
// 可见时写空串而非 'block':display 由样式类决定(.ce_row/.ce_column 为 flex),
|
|
242
|
+
// 内联 block 会盖掉 flex,导致 justify/align 失效(同 h5.js applyDirectives 注释)。
|
|
241
243
|
const expression = conditionMap.expression
|
|
242
244
|
if (hasText(expression)) {
|
|
243
|
-
if (showType === '01') extend.display = `(${expression}) ? '
|
|
244
|
-
else if (showType === '02') extend.display = `(${expression}) ? 'none' : '
|
|
245
|
+
if (showType === '01') extend.display = `(${expression}) ? '' : 'none'`
|
|
246
|
+
else if (showType === '02') extend.display = `(${expression}) ? 'none' : ''`
|
|
245
247
|
return
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
if (Array.isArray(conditionMap.group) && conditionMap.group.length > 0) {
|
|
249
251
|
const expr = convertCondition(conditionMap.group)
|
|
250
|
-
if (showType === '01') extend.display = `${expr} ? '
|
|
251
|
-
else if (showType === '02') extend.display = `${expr} ? 'none' : '
|
|
252
|
+
if (showType === '01') extend.display = `${expr} ? '' : 'none'`
|
|
253
|
+
else if (showType === '02') extend.display = `${expr} ? 'none' : ''`
|
|
252
254
|
return
|
|
253
255
|
}
|
|
254
256
|
|
|
@@ -116,6 +116,51 @@ export function collectProps(properties = []) {
|
|
|
116
116
|
|
|
117
117
|
const IDENT_RE = /^[A-Za-z0-9_$]+$/
|
|
118
118
|
|
|
119
|
+
/* ------------------------ fastjson / HashMap 键序 ------------------------ */
|
|
120
|
+
|
|
121
|
+
/** Java String.hashCode */
|
|
122
|
+
function javaStringHash(text) {
|
|
123
|
+
let h = 0
|
|
124
|
+
for (let i = 0; i < text.length; i++) h = (Math.imul(31, h) + text.charCodeAt(i)) | 0
|
|
125
|
+
return h
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** HashMap 的扰动函数:h ^ (h >>> 16) */
|
|
129
|
+
function hashSpread(h) {
|
|
130
|
+
return (h ^ (h >>> 16)) >>> 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 按元素个数推算 HashMap 表容量(默认 16,负载因子 0.75,翻倍扩容) */
|
|
134
|
+
function hashCapacity(size) {
|
|
135
|
+
let cap = 16
|
|
136
|
+
while (size > cap * 0.75) cap *= 2
|
|
137
|
+
return cap
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 复现 Java HashMap 的迭代顺序
|
|
142
|
+
*
|
|
143
|
+
* 平台产物里的 JSON 由 fastjson 的 `JSONObject`(底层 HashMap)序列化,键序是**哈希桶顺序**
|
|
144
|
+
* 而不是插入顺序。产物只用于「替换线上文件」时,键序语义上无所谓;但只要希望与平台产物
|
|
145
|
+
* 逐字节一致(便于 diff 对拍),就必须复现这个顺序。
|
|
146
|
+
*
|
|
147
|
+
* 规则:cap = 16 起按 0.75 负载因子扩容;桶下标 = (h ^ h>>>16) & (cap-1);
|
|
148
|
+
* 按下标升序输出,同桶内保持插入顺序。
|
|
149
|
+
*/
|
|
150
|
+
export function javaHashMapOrder(value) {
|
|
151
|
+
if (Array.isArray(value)) return value.map(javaHashMapOrder)
|
|
152
|
+
if (!value || typeof value !== 'object') return value
|
|
153
|
+
const entries = Object.entries(value)
|
|
154
|
+
const cap = hashCapacity(entries.length)
|
|
155
|
+
return entries
|
|
156
|
+
.map(([k, v], i) => ({ k, v: javaHashMapOrder(v), i, bucket: hashSpread(javaStringHash(k)) & (cap - 1) }))
|
|
157
|
+
.sort((a, b) => a.bucket - b.bucket || a.i - b.i)
|
|
158
|
+
.reduce((acc, x) => {
|
|
159
|
+
acc[x.k] = x.v
|
|
160
|
+
return acc
|
|
161
|
+
}, {})
|
|
162
|
+
}
|
|
163
|
+
|
|
119
164
|
/**
|
|
120
165
|
* JS 对象字面量的键名写法(平台口径)
|
|
121
166
|
*
|
|
@@ -143,7 +188,7 @@ export function convertPropsH5(properties = []) {
|
|
|
143
188
|
parts.push(`${key}:${convertPropsH5(prop.children)}`)
|
|
144
189
|
} else if (prop.list && prop.list.length > 0) {
|
|
145
190
|
const groups = prop.list.map((g) => convertPropsH5(g))
|
|
146
|
-
parts.push(`${key}:[${groups.join(',')}]`)
|
|
191
|
+
parts.push(`${key}: [${groups.join(',')}]`)
|
|
147
192
|
} else {
|
|
148
193
|
parts.push(`${key}:${JSON.stringify(prop.itemValue)}`)
|
|
149
194
|
}
|
|
@@ -161,7 +206,7 @@ export function convertPropsH5(properties = []) {
|
|
|
161
206
|
/**
|
|
162
207
|
* `$foo` → `s.$foo`
|
|
163
208
|
*
|
|
164
|
-
* 平台卡片状态挂在上下文实例的 `
|
|
209
|
+
* 平台卡片状态挂在上下文实例的 `s` 上(生成代码里 `var s = c.s`,ExpressionState),
|
|
165
210
|
* 所以表达式里的 `$x` 必须改写为 `s.$x`。
|
|
166
211
|
* 字符串字面量('…' / "…" / `…`)内部的 `$` 不得改写。
|
|
167
212
|
*
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* 元素组件通过 `this.getElement('Text')` 取宿主 elements 注册表。
|
|
15
15
|
*
|
|
16
16
|
* 关键契约(照抄平台侧,改动会直接导致真机渲染空白):
|
|
17
|
-
* - 数据挂在 `s = c.
|
|
17
|
+
* - 数据挂在 `s = c.s`(ExpressionState,提供 $key / $$),表达式里的 `$foo` 已改写为 `s.$foo`;
|
|
18
|
+
* 不能用 `c.state` —— 那是 MethodState,只有平铺键名,`s.$foo` 会一律求值为 undefined;
|
|
18
19
|
* - `data` 里必须同时给 defaultState / state / n(n 是刷新因子,模板里靠它触发重算);
|
|
19
20
|
* - watch.data 重新合并 state,宿主下发新数据时才能刷新;
|
|
20
21
|
* - 每个事件的实现体被搬到 methods 里,on 只留转发(避免闭包捕获旧 state)。
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
convertPropsClient,
|
|
28
29
|
convertPropsH5,
|
|
29
30
|
hasText,
|
|
31
|
+
javaHashMapOrder,
|
|
30
32
|
jsKey,
|
|
31
33
|
pickVarProps,
|
|
32
34
|
} from './common.js'
|
|
@@ -89,8 +91,12 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
89
91
|
for (const child of card.components) slot.components.push(convertComponent(child))
|
|
90
92
|
|
|
91
93
|
function convertComponent(component) {
|
|
94
|
+
const desc = elementOf(component.className)
|
|
92
95
|
const layer = {
|
|
93
|
-
|
|
96
|
+
// 列表 / 网格在平台侧会先被客户端编译改写成 `<cardCode>_sub_<componentId>`(共享同一份
|
|
97
|
+
// CardModel,H5 编译在其后,于是把这个改写后的 id 带进了 ref)。此处等价复现:
|
|
98
|
+
// 只影响容器自身的 ref,循环项内部的组件 id 保持原样。
|
|
99
|
+
id: desc.isNewTemplate ? `${card.code}_sub_${component.componentId}` : component.componentId,
|
|
94
100
|
name: component.className,
|
|
95
101
|
style: {},
|
|
96
102
|
props: convertPropsH5(component.properties),
|
|
@@ -148,8 +154,12 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
148
154
|
return
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
// 可见时不能写死 `block`:布局元素的 display 由样式类决定(`.ce_row`/`.ce_column`
|
|
158
|
+
// 是 flex),内联 `display:block` 会盖掉 flex → justifyContent/alignItems 全失效。
|
|
159
|
+
// accountCreditedlist 的「更多转账伙伴」行就是被它压成 block,文字靠左 + 图标换行被裁掉。
|
|
160
|
+
// 空串等价于 v-show 的「显隐只切 none」——清掉内联值,恢复原生 display。
|
|
161
|
+
if (showType === '01') layer.style.display = `(${expression}) ? '' : 'none'`
|
|
162
|
+
else if (showType === '02') layer.style.display = `(${expression}) ? 'none' : ''`
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
/**
|
|
@@ -190,9 +200,16 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
190
200
|
const fn = constProps.function
|
|
191
201
|
const code = fn && typeof fn === 'object' ? fn.code : null
|
|
192
202
|
const compiled = fn && typeof fn === 'object' ? fn.compiled : null
|
|
193
|
-
//
|
|
194
|
-
|
|
203
|
+
// 取 code 优先,compiled 只作兜底 —— 与编辑器预览(RenderTree.actionCode)和
|
|
204
|
+
// 运行时(preview 的 actionCode,`code || compiled`)保持同一优先级。
|
|
205
|
+
//
|
|
206
|
+
// 反过来(compiled 优先)会让「编辑器里改好、导出产物还是旧的」:两者是双份数据,
|
|
207
|
+
// compiled 是平台侧某次转译留下的快照,编辑器改 code 时它不会跟着变(SetCode 只覆写 code;
|
|
208
|
+
// ai-chat 的自动同步也只在「原本就同步」时生效)。实例:account 的 onMeasured 标题脚本,
|
|
209
|
+
// code 已是左对齐 `600 17px` / 浅绿 #D8F5E8 / x=0,compiled 仍停在居中的 `bold 16px` /
|
|
210
|
+
// #B9D0FA / x=canvas.width/2,产物跑旧版,标题位置与配色全错。
|
|
195
211
|
if (hasText(code)) return code
|
|
212
|
+
if (hasText(compiled)) return compiled
|
|
196
213
|
warnings.push(`事件 ${eventAction.eventKey} 的 customAction 缺少 code,已跳过`)
|
|
197
214
|
return ''
|
|
198
215
|
}
|
|
@@ -241,9 +258,9 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
241
258
|
const code = scopeSlots
|
|
242
259
|
.map(
|
|
243
260
|
(s) =>
|
|
244
|
-
`${jsKey(s.slotKey)}:function(_sd_){return (function(c){var s = c.
|
|
261
|
+
`${jsKey(s.slotKey)}:function(_sd_){return (function(c){var s = c.s; return [${s.components
|
|
245
262
|
.map(serializeComponent)
|
|
246
|
-
.join(',')}]})(new CC(_vm,_sd_,c))}`
|
|
263
|
+
.join(',')}]})(new CC(_vm,_sd_,c)) }`
|
|
247
264
|
)
|
|
248
265
|
.join(',')
|
|
249
266
|
options.push(`scopedSlots:{${code}}`)
|
|
@@ -271,7 +288,9 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
271
288
|
"var CC = this.getObject('context');" +
|
|
272
289
|
'var e = this.getElement;' +
|
|
273
290
|
'var c = new CC(this,this);' +
|
|
274
|
-
|
|
291
|
+
// 必须是 c.s(ExpressionState,提供 $key / $$ 读取),不能用 c.state(MethodState,只有平铺键名)。
|
|
292
|
+
// 平台 Context 里两者并存:s 走表达式读、state 走方法写。写成 c.state 会让所有 s.$xxx 求值为 undefined。
|
|
293
|
+
'var s = c.s;' +
|
|
275
294
|
'var n = this.n;' +
|
|
276
295
|
`return ${components.map(serializeComponent).join('')}` +
|
|
277
296
|
'}'
|
|
@@ -286,7 +305,8 @@ export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
|
286
305
|
parts.push('props:{data:{type:Object,default:function(){ return {} }}}')
|
|
287
306
|
|
|
288
307
|
const dataCode = [
|
|
289
|
-
|
|
308
|
+
// 键序按 Java HashMap 桶序输出:与平台产物逐字节一致(语义等价,仅便于 diff 对拍)
|
|
309
|
+
`defaultState:${JSON.stringify(javaHashMapOrder(defaultState))}`,
|
|
290
310
|
'state:{}',
|
|
291
311
|
'n:0',
|
|
292
312
|
].join(',')
|
|
@@ -169,7 +169,7 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
169
169
|
```js
|
|
170
170
|
// 仅当需要自定义页面交互/逐样式断言且宿主 dev server(5180) 已启动时使用;
|
|
171
171
|
// 本 skill 的离线 render_card.mjs 不提供任意页面交互,复制 skill 到其他项目后该配方按需自建。
|
|
172
|
-
import { chromium } from 'playwright';
|
|
172
|
+
import { chromium } from 'playwright-core';
|
|
173
173
|
const ts = Date.now();
|
|
174
174
|
const url = `http://localhost:5180/?card=<id>&t=${ts}`;
|
|
175
175
|
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// MBank mockup 截图工具:把 375×812 的 mockup HTML 导出为 750×1624 @2x PNG。
|
|
3
3
|
// 用法:node <skill>/tools/screenshot.mjs <input.html> [output.png] [--full]
|
|
4
4
|
// --full 整页截图(长页 mockup 用;默认 812pt 视口截断)
|
|
5
|
-
// 依赖 playwright
|
|
5
|
+
// 依赖 playwright-core(免浏览器下载的纯 JS API,从自身位置向上解析)。
|
|
6
|
+
// 浏览器优先走系统 Chrome(channel: 'chrome',仓库约定),未找到时才尝试内置浏览器。
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
7
8
|
import { dirname, resolve } from 'node:path';
|
|
8
9
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,7 +11,15 @@ import { pathToFileURL } from 'node:url';
|
|
|
10
11
|
|
|
11
12
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
|
-
|
|
14
|
+
let chromium;
|
|
15
|
+
try {
|
|
16
|
+
({ chromium } = require('playwright-core'));
|
|
17
|
+
} catch {
|
|
18
|
+
console.error(
|
|
19
|
+
'[screenshot] 缺少依赖 playwright-core:请在本工程根执行 npm install(cardbuddy 依赖会带上),或手动 npm i playwright-core。',
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
const args = process.argv.slice(2);
|
|
16
25
|
const full = args.includes('--full');
|
|
@@ -22,15 +31,14 @@ if (!input) {
|
|
|
22
31
|
}
|
|
23
32
|
const output = positional[1] || input.replace(/\.html$/i, '') + '.png';
|
|
24
33
|
|
|
25
|
-
//
|
|
26
|
-
// 与仓库「Playwright + 系统 Chrome 验证」约定一致,避免强依赖 npx playwright install。
|
|
34
|
+
// 优先用系统 Chrome(channel: 'chrome',仓库约定);不可用时回退内置浏览器。
|
|
27
35
|
let browser;
|
|
28
36
|
try {
|
|
29
|
-
browser = await chromium.launch();
|
|
37
|
+
browser = await chromium.launch({ channel: 'chrome' });
|
|
30
38
|
} catch (err) {
|
|
31
39
|
if (!/Executable doesn't exist/.test(String(err?.message ?? err))) throw err;
|
|
32
|
-
console.error('[screenshot]
|
|
33
|
-
browser = await chromium.launch(
|
|
40
|
+
console.error('[screenshot] 未找到系统 Chrome,回退 Playwright 内置浏览器');
|
|
41
|
+
browser = await chromium.launch();
|
|
34
42
|
}
|
|
35
43
|
const page = await browser.newPage({
|
|
36
44
|
viewport: { width: 375, height: 812 },
|
|
@@ -29,10 +29,18 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
29
29
|
const skillDir = path.resolve(scriptDir, '..');
|
|
30
30
|
// skill now lives under <repo-root>/skills/<name>: repo root is two levels up from skillDir
|
|
31
31
|
const workspace = path.resolve(skillDir, '..', '..');
|
|
32
|
-
// 与 platform_login.mjs 保持一致的解析方式:用裸标识符让 Node 从项目根 node_modules
|
|
33
|
-
// playwright
|
|
34
|
-
//
|
|
35
|
-
|
|
32
|
+
// 与 platform_login.mjs 保持一致的解析方式:用裸标识符让 Node 从项目根 node_modules 解析。
|
|
33
|
+
// 用 playwright-core(免浏览器下载的纯 JS API):浏览器走系统 Chrome + CDP,
|
|
34
|
+
// 不需要 playwright 自带浏览器;playwright 作为 optional 依赖安装失败时会被 npm
|
|
35
|
+
// 静默跳过,导致运行时报 Cannot find package(0.83.x 事故)。
|
|
36
|
+
let chromium;
|
|
37
|
+
try {
|
|
38
|
+
({ chromium } = await import('playwright-core'));
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'缺少依赖 playwright-core:请在本工程根执行 npm install(cardbuddy 依赖会带上 playwright-core),或手动 npm i playwright-core。',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
36
44
|
|
|
37
45
|
// 统一日志美化(容错:缺 server/core/logger 时回退极简实现,保证 demo / 老项目不崩)。
|
|
38
46
|
// 实时项目与 build 后的 dist/server-core 均含该模块;demo 等缺少 server/ 目录的场景走 fallback。
|
|
@@ -8,22 +8,7 @@
|
|
|
8
8
|
"name": "platform-session-navigator-scripts",
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"playwright": "^1.62.1"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"node_modules/playwright": {
|
|
15
|
-
"version": "1.64.1",
|
|
16
|
-
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.64.1.tgz",
|
|
17
|
-
"integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==",
|
|
18
|
-
"license": "Apache-2.0",
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"playwright-core": "1.64.1"
|
|
21
|
-
},
|
|
22
|
-
"bin": {
|
|
23
|
-
"playwright": "cli.js"
|
|
24
|
-
},
|
|
25
|
-
"engines": {
|
|
26
|
-
"node": ">=20"
|
|
11
|
+
"playwright-core": "^1.62.1"
|
|
27
12
|
}
|
|
28
13
|
},
|
|
29
14
|
"node_modules/playwright-core": {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "playwright-core-based 远端自动登录脚本(纯 JS API,浏览器走系统 Chrome + CDP)",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"playwright": "^1.62.1"
|
|
8
|
+
"playwright-core": "^1.62.1"
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// 依赖只取 playwright 的 JS API(浏览器由本脚本 spawn 系统 Chrome + connectOverCDP 连接,
|
|
2
|
+
// 不需要 playwright 自带浏览器),因此用免下载的 playwright-core:它是 cardbuddy 与工程
|
|
3
|
+
// devDependencies 里的普通依赖,npm install 即落地;不用 playwright(postinstall 下载
|
|
4
|
+
// 数百 MB 浏览器,作为 optionalDependency 安装失败时会被 npm 静默跳过,错误延迟到运行时)。
|
|
5
|
+
let chromium;
|
|
6
|
+
try {
|
|
7
|
+
({ chromium } = await import("playwright-core"));
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"缺少依赖 playwright-core:请在本工程根执行 npm install(cardbuddy 依赖会带上 playwright-core),或手动 npm i playwright-core。",
|
|
11
|
+
);
|
|
12
|
+
}
|
|
2
13
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
3
14
|
import { existsSync } from "node:fs";
|
|
4
15
|
import { spawn } from "node:child_process";
|
|
@@ -58,7 +58,7 @@ let _sessionCache = null; // { browser, login, at }
|
|
|
58
58
|
* @param {object} [options]
|
|
59
59
|
* @param {number} [options.debugPort=SYNC_PORT]
|
|
60
60
|
* @param {boolean} [options.headless=true]
|
|
61
|
-
* @returns {Promise<{ browser: import('playwright').Browser, login: object }>}
|
|
61
|
+
* @returns {Promise<{ browser: import('playwright-core').Browser, login: object }>}
|
|
62
62
|
*/
|
|
63
63
|
export async function ensurePlatformSession({ debugPort = SYNC_PORT, headless = true } = {}) {
|
|
64
64
|
// 热路径:进程内缓存未过期且 CDP 仍连通 → 直接复用(跳过 runPlatformLogin 的
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Local dev service for the CardBuddy card editor — decouples card CRUD (sync/save/validate/render-status/screenshot) from the static public editor, so the editor can be deployed to any static host while each developer runs a local service against their own card source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,14 +30,12 @@
|
|
|
30
30
|
"codejar": "^4.3.0",
|
|
31
31
|
"connect": "^3.7.0",
|
|
32
32
|
"dotenv": "^18.0.9",
|
|
33
|
+
"playwright-core": "^1.62.1",
|
|
33
34
|
"pngjs": "^7.0.0",
|
|
34
35
|
"prismjs": "^1.30.0",
|
|
35
36
|
"qrcode": "^1.5.4",
|
|
36
37
|
"selfsigned": "^4.0.1",
|
|
37
38
|
"vite": "^8.2.0",
|
|
38
39
|
"vue": "^3.5.40"
|
|
39
|
-
},
|
|
40
|
-
"optionalDependencies": {
|
|
41
|
-
"playwright": "^1.62.1"
|
|
42
40
|
}
|
|
43
41
|
}
|