cardbuddy 0.24.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.
@@ -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-BumNBo-n.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-DL4ZCJCA.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-MWKPP9nd.css">
13
13
  </head>
14
14
  <body>
@@ -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
- if (!chromiumMod) chromiumMod = await import('playwright')
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()
@@ -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(仓库根 node_modules 已装;本脚本从自身位置向上解析)。
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
- const { chromium } = require('playwright');
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
- // 优先用 Playwright 自带浏览器;未下载(ms-playwright 缓存为空)时回退系统 Chrome,
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] 未找到 Playwright 内置浏览器,回退系统 Chrome');
33
- browser = await chromium.launch({ channel: 'chrome' });
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。原硬编码路径指向 .workbuddy/skills/platform-session-navigator/scripts/node_modules
34
- // (该 node_modules 是已失效的断链符号链接),导致「Cannot find module」报错。
35
- const { chromium } = await import('playwright');
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": "Playwright-based 远端 UAT auto-login scripts",
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
- import { chromium } from "playwright";
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.24.0",
3
+ "version": "0.25.0",
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
  }