cardbuddy 0.9.0 → 0.10.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.
Files changed (32) hide show
  1. package/README.md +17 -2
  2. package/dist/cli.js +3 -0
  3. package/dist/config.js +7 -0
  4. package/dist/portGuard.js +293 -0
  5. package/dist/preview-editor/assets/index-C_UKS0NF.css +1 -0
  6. package/dist/preview-editor/assets/index-D1iIJ4sl.js +81 -0
  7. package/dist/preview-editor/index.html +2 -2
  8. package/dist/server-core/assetProxy.js +3 -22
  9. package/dist/server-core/buildDataFromSchema.js +36 -4
  10. package/dist/server-core/cardListCache.js +0 -17
  11. package/dist/server-core/index.js +3 -5
  12. package/dist/server-core/logStream.js +1 -6
  13. package/dist/standalone.js +24 -8
  14. package/dist/vendored/App.vue +187 -77
  15. package/dist/vendored/components/ComponentPalette.vue +1 -1
  16. package/dist/vendored/components/DebugPanel.vue +737 -91
  17. package/dist/vendored/components/NumberStepper.vue +125 -0
  18. package/dist/vendored/components/ProgressSlider.vue +9 -1
  19. package/dist/vendored/components/StructureTree.vue +2 -2
  20. package/dist/vendored/runtime/clipboard-node.js +0 -5
  21. package/dist/vendored/styles/app.css +707 -252
  22. package/dist/vendored/styles/card-gallery.css +3 -3
  23. package/dist/vendored/styles/card-select.css +13 -6
  24. package/dist/vendored/styles/dropdown.css +1 -2
  25. package/dist/vendored/styles/json-tree.css +3 -3
  26. package/dist/vendored/styles/node-inspector.css +7 -294
  27. package/dist/vendored/styles/setters.css +48 -49
  28. package/dist/vendored/styles/structure-tree.css +6 -11
  29. package/package.json +1 -1
  30. package/dist/preview-editor/assets/index-CrcrVs4F.css +0 -1
  31. package/dist/preview-editor/assets/index-DnpHHKWl.js +0 -80
  32. package/dist/vendored/assets/logo.png +0 -0
@@ -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-DnpHHKWl.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-CrcrVs4F.css">
11
+ <script type="module" crossorigin src="/assets/index-D1iIJ4sl.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-C_UKS0NF.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="app"></div>
@@ -87,13 +87,9 @@ async function fetchUpstream(pathname, upstream) {
87
87
  }
88
88
 
89
89
  /**
90
- * 资源代理 + 落盘缓存(回源地址从传入的 upstream 取)。
91
- * @param {{ root: string, upstream: string }} opts
92
- */
93
- /**
94
- * 资源代理 + 落盘缓存中间件(与 Express / Vite middleware 容器解耦)。
95
- * 既可由 Vite 插件 configureServer 挂载,也可由 server/core 的 mountCoreMiddlewares
96
- * 直接挂载到 cardbuddy(8787),实现「Vite 零后端插件、8787 是唯一后端」。
90
+ * 资源代理 + 落盘缓存中间件(回源地址从传入的 upstream 取;与 Express / Vite middleware 容器解耦)。
91
+ * 由 server/core 的 mountCoreMiddlewares 挂载到 cardbuddy(8787),
92
+ * 实现「Vite 零后端插件、8787 是唯一后端」。
97
93
  * @param {{ root: string, upstream: string }} opts
98
94
  * @returns {(req: import('http').IncomingMessage, res: import('http').ServerResponse, next: Function) => void}
99
95
  */
@@ -137,18 +133,3 @@ export function createAssetProxyMiddleware({ root, upstream }) {
137
133
  return res.end(PLACEHOLDER)
138
134
  }
139
135
  }
140
-
141
- /**
142
- * Vite 插件适配层(保留以兼容纯 vite dev 场景):configureServer 阶段把
143
- * createAssetProxyMiddleware 挂到 server.middlewares。前后分离架构下已由 server/core
144
- * 的 mountCoreMiddlewares 挂载到 cardbuddy(8787),Vite 不再需要此插件。
145
- * @param {{ root: string, upstream: string }} opts
146
- */
147
- export function createAssetProxyPlugin({ root, upstream }) {
148
- return {
149
- name: 'finmall-asset-proxy',
150
- configureServer(server) {
151
- server.middlewares.use(createAssetProxyMiddleware({ root, upstream }))
152
- },
153
- }
154
- }
@@ -8,13 +8,21 @@
8
8
  * 仅当 defaultValue 为空 / 空对象 / 未定义时,回退到 param.desc 里的 JSON 兜底
9
9
  * (desc 以 '{' 开头且解析后含该 key 时生效)。
10
10
  *
11
+ * mock 覆盖(dictionary.mocks):
12
+ * 平台变量面板的「配置变量 mock」把设计期覆盖值写在 dictionary.mocks(key → 值),
13
+ * 与「默认值」inputParams[].defaultValue 分仓:defaultValue 是随卡发布的缺省数据,
14
+ * mocks 仅供设计期预览覆盖(编辑器画布),不进入 data.json。故默认(options.includeMocks
15
+ * falsy)只按 defaultValue 派生 —— 服务端写盘 / 同步落盘走这条;编辑器预览态传
16
+ * { includeMocks: true },在 defaultValue 之上按 key 叠加非空 mock 值。
17
+ *
11
18
  * 本文件是纯 ESM(无任何 node / 浏览器专有依赖),因此既能被服务端(Node)import,
12
19
  * 也能被预览前端(Vite 打包)直接 import,避免逻辑漂移。
13
20
  */
14
- export function buildDataFromSchema(schema) {
21
+ export function buildDataFromSchema(schema, options = {}) {
15
22
  // 稳健性:schema 缺失 / 非对象(如外部改坏 schema.json)时返回空对象,
16
23
  // 不抛错,避免 GET /__card 在损坏卡片上返回 500 堆栈。
17
24
  if (!schema || typeof schema !== 'object') return {}
25
+ const mocks = options.includeMocks ? readMocks(schema) : null
18
26
  const data = {}
19
27
  for (const param of schema.dictionary?.inputParams || []) {
20
28
  const fallback = parseDescFallback(param)
@@ -24,15 +32,39 @@ export function buildDataFromSchema(schema) {
24
32
  typeof defaultValue === 'object' &&
25
33
  !Array.isArray(defaultValue) &&
26
34
  Object.keys(defaultValue).length === 0
27
- data[param.key] = clone(
35
+ let value =
28
36
  fallback !== undefined && (defaultValue === undefined || defaultValue === '' || emptyObject)
29
37
  ? fallback
30
- : defaultValue,
31
- )
38
+ : defaultValue
39
+ // mock 覆盖优先于默认值;空 mock(未配置)不覆盖,回退 defaultValue / desc 兜底
40
+ if (mocks && hasMockValue(mocks, param.key)) value = mocks[param.key]
41
+ data[param.key] = clone(value)
32
42
  }
33
43
  return data
34
44
  }
35
45
 
46
+ /**
47
+ * 读取 dictionary.mocks(key → mock 值)。历史卡 / 同步脚本可能写成数组或缺省,
48
+ * 一律归一为 null(视为「没有 mock 表」),调用方无需再判形。
49
+ */
50
+ export function readMocks(schema) {
51
+ const mocks = schema && schema.dictionary && schema.dictionary.mocks
52
+ return mocks && typeof mocks === 'object' && !Array.isArray(mocks) ? mocks : null
53
+ }
54
+
55
+ /**
56
+ * 某 key 是否「配置了 mock」:undefined / null / 空串 / 空容器 视为未配置
57
+ * (与变量列表的 m 徽标同一判据,避免空壳 mock 被当成有效覆盖)。
58
+ */
59
+ export function hasMockValue(mocks, key) {
60
+ if (!mocks || !Object.prototype.hasOwnProperty.call(mocks, key)) return false
61
+ const value = mocks[key]
62
+ if (value === undefined || value === null || value === '') return false
63
+ if (Array.isArray(value)) return value.length > 0
64
+ if (typeof value === 'object') return Object.keys(value).length > 0
65
+ return true
66
+ }
67
+
36
68
  function parseDescFallback(param) {
37
69
  if (!param.desc || typeof param.desc !== 'string') return undefined
38
70
  const text = param.desc.trim()
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  const cardIndex = new Map(); // cardId(String) -> { cardId, code, cardName }
16
- let lastRefreshedAt = 0;
17
16
 
18
17
  /**
19
18
  * 写入/合并卡片列表。
@@ -27,7 +26,6 @@ export function refreshCardList(cards, { full = false } = {}) {
27
26
  const id = String(c.cardId);
28
27
  cardIndex.set(id, { cardId: id, code: c.code, cardName: c.cardName });
29
28
  }
30
- lastRefreshedAt = Date.now();
31
29
  }
32
30
 
33
31
  /** 卡片是否存在于缓存列表(按 cardId / pageId 查)。 */
@@ -35,16 +33,6 @@ export function hasCardId(cardId) {
35
33
  return cardIndex.has(String(cardId));
36
34
  }
37
35
 
38
- /** 取缓存中的卡片元信息,不存在返回 null。 */
39
- export function getCard(cardId) {
40
- return cardIndex.get(String(cardId)) || null;
41
- }
42
-
43
- /** 缓存是否已非空(至少同步过一次全量列表)。 */
44
- export function cacheSize() {
45
- return cardIndex.size;
46
- }
47
-
48
36
  /**
49
37
  * 推送前存在性校验:目标卡片是否确实在远端卡片列表中(按 cardId/pageId 查)。
50
38
  * - 缓存为空(本次 dev server 尚未全量同步过):放行,返回 allowed=true,避免误拦截首推。
@@ -61,8 +49,3 @@ export function checkPushAllowed(pageId) {
61
49
  reason: `卡片 pageId=${pageId} 不在远端卡片列表中,疑似已删除或 pageId 错位,请先「远程同步」刷新列表后再推送`,
62
50
  };
63
51
  }
64
-
65
- /** 上次刷新时间戳(ms),未刷新为 0。 */
66
- export function lastRefreshed() {
67
- return lastRefreshedAt;
68
- }
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * 设计:
5
5
  * - 每个端点都是独立 `(req, res, next)` 中间件工厂(见同目录各文件),与 Express / Vite
6
- * 中间件容器解耦,因此可被两处复用:
7
- * 1) Vite 插件(server/cardServer.js,编辑器内核开发 same-origin 路径);
8
- * 2) 独立本地开发服务(packages/cardbuddy,公网静态编辑器指向本机路径)。
9
- * - mountCoreMiddlewares(app, opts) 把全部中间件挂到任意 Connect 风格容器(Vite 的
10
- * server.middlewares 或 connect 实例皆可),两处共用、改一处两处同步。
6
+ * 中间件容器解耦,由本地开发服务 packages/cardbuddy 统一装配(dev 与发布态同一份)。
7
+ * - mountCoreMiddlewares(app, opts) 把全部中间件挂到任意 Connect 风格容器(cardbuddy 的
8
+ * connect 实例,或 tools/regress-data.mjs 里的临时容器),改一处全链路同步。
11
9
  *
12
10
  * 注意:renderStatus / nodeModels 需要活着的 vite 实例做 ssrLoadModule,故 mountCoreMiddlewares
13
11
  * 接收 viteServer 参数透传;无 SSR 需求的场景可传 null(这两个端点会返回明确错误)。
@@ -166,11 +166,6 @@ export function subscribeLog(cb) {
166
166
  return () => subscribers.delete(cb)
167
167
  }
168
168
 
169
- /** 当前订阅者数量(诊断用)。 */
170
- export function logSubscriberCount() {
171
- return subscribers.size
172
- }
173
-
174
169
  let mirrorInstalled = false
175
170
 
176
171
  /**
@@ -218,7 +213,7 @@ export function installLogMirror() {
218
213
 
219
214
  /**
220
215
  * 日志流 HTTP 端点:GET /__logs(历史/补漏)、POST /__logs/ingest(外部进程注入)。
221
- * 挂在 mountCoreMiddlewares 里,因此 Vite 插件形态与 cardbuddy 独立服务形态同时可用。
216
+ * 挂在 mountCoreMiddlewares 里,随 cardbuddy 本地服务一并生效。
222
217
  */
223
218
  export function createLogMiddleware() {
224
219
  const sendJson = (res, status, payload) => {
@@ -24,6 +24,7 @@ import chalk from 'chalk'
24
24
 
25
25
  import { mountCoreMiddlewares, installLogMirror, subscribeLog } from './core.js'
26
26
  import { loadConfig } from './config.js'
27
+ import { ensurePortFree } from './portGuard.js'
27
28
 
28
29
  const MIME_TYPES = {
29
30
  '.html': 'text/html; charset=utf-8',
@@ -45,6 +46,13 @@ const MIME_TYPES = {
45
46
  '.map': 'application/json; charset=utf-8',
46
47
  }
47
48
 
49
+ // 被 dev.mjs 拉起时(DSL_LAUNCHER='dev'),来源标签由启动器用带背景色的 chip 标注,
50
+ // 这里让出前缀避免双重来源标签;直接运行 / dev:prod(stdio:inherit)则自带标签。
51
+ const srcTag =
52
+ process.env.DSL_LAUNCHER === 'dev'
53
+ ? ''
54
+ : chalk.magenta('▌ ') + chalk.magenta.bold('cardbuddy') + ' '
55
+
48
56
  function decodePathname(url) {
49
57
  if (!url) return '/'
50
58
  const raw = url.split('?')[0]
@@ -382,6 +390,21 @@ async function resolveTls(config) {
382
390
  */
383
391
  export async function startServer(userConfig = {}) {
384
392
  const config = { ...loadConfig(), ...userConfig }
393
+
394
+ // 日志镜像尽早安装(幂等,CLI 入口已先行装过一次):让端口接管等启动期日志也能进入
395
+ // 编辑器底部「服务日志」面板的环形缓冲。
396
+ installLogMirror()
397
+
398
+ // 端口前置检查:被「上一个 cardbuddy 实例」占用时主动结束它再接管(见 src/portGuard.js)。
399
+ // 放在最前面,避免白跑一遍 TLS / Vite 初始化后才以 EADDRINUSE 失败。
400
+ await ensurePortFree({
401
+ port: config.port,
402
+ tls: config.https,
403
+ token: config.token,
404
+ mode: config.portTakeover,
405
+ log: (msg) => console.log(srcTag + msg),
406
+ })
407
+
385
408
  const app = connect()
386
409
 
387
410
  app.use(corsMiddleware)
@@ -403,8 +426,7 @@ export async function startServer(userConfig = {}) {
403
426
 
404
427
  // 服务日志流:镜像本进程 stdout/stderr(含 chalk ANSI)→ 环形缓冲 → SSE 批量推送。
405
428
  // 编辑器底部「服务日志」面板订阅 event: service-log;历史与断线补漏走 GET /__logs。
406
- // installLogMirror 幂等,CLI 入口已先行安装,这里兜住以编程方式调用 startServer 的场景。
407
- installLogMirror()
429
+ // 镜像本体在函数开头安装,这里只负责把新增行转推给 SSE 订阅者。
408
430
  subscribeLog((lines) => events.notify({ lines }, 'service-log'))
409
431
 
410
432
  const vite = await createEmbeddedVite(config)
@@ -437,12 +459,6 @@ export async function startServer(userConfig = {}) {
437
459
  })
438
460
 
439
461
  const bind = `${scheme}://127.0.0.1:${config.port}`
440
- // 被 dev.mjs 拉起时(DSL_LAUNCHER='dev'),来源标签由启动器用带背景色的 chip 标注,
441
- // 这里让出前缀避免双重来源标签;直接运行 / dev:prod(stdio:inherit)则自带标签。
442
- const srcTag =
443
- process.env.DSL_LAUNCHER === 'dev'
444
- ? ''
445
- : chalk.magenta('▌ ') + chalk.magenta.bold('cardbuddy') + ' '
446
462
  console.log(
447
463
  srcTag +
448
464
  chalk.green(`本地开发服务已启动:${bind}`) +