cardbuddy 0.22.0 → 0.24.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 (76) hide show
  1. package/README.md +11 -11
  2. package/dist/cli.js +2 -2
  3. package/dist/config.js +3 -3
  4. package/dist/init.js +13 -13
  5. package/dist/mcp.js +1 -1
  6. package/dist/preview-editor/assets/{index-CPpLZZyi.js → index-BumNBo-n.js} +81 -80
  7. package/dist/preview-editor/assets/{index-CU8zVA2P.css → index-MWKPP9nd.css} +1 -1
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/skills-upgrade.js +1 -1
  10. package/dist/standalone.js +2 -2
  11. package/dist/vendored/card-editor/runtime/adapter.js +10 -11
  12. package/dist/vendored/mcp/client.mjs +50 -13
  13. package/dist/vendored/mcp/tools.mjs +13 -7
  14. package/dist/vendored/server/core/ai-chat.js +540 -183
  15. package/dist/vendored/server/core/assetProxy.js +1 -1
  16. package/dist/vendored/server/core/browser.js +1 -1
  17. package/dist/vendored/server/core/cardCompile.js +202 -0
  18. package/dist/vendored/server/core/cardDigest.js +324 -0
  19. package/dist/vendored/server/core/cardExport.js +5 -5
  20. package/dist/vendored/server/core/cardFiles.js +1 -1
  21. package/dist/vendored/server/core/cardHistoryRemote.js +1 -1
  22. package/dist/vendored/server/core/cardImportMeta.js +1 -1
  23. package/dist/vendored/server/core/cardListCache.js +92 -10
  24. package/dist/vendored/server/core/cardPublish.js +11 -6
  25. package/dist/vendored/server/core/cardPublishVersions.js +8 -8
  26. package/dist/vendored/server/core/cardSave.js +14 -10
  27. package/dist/vendored/server/core/cardSidecar.js +3 -3
  28. package/dist/vendored/server/core/cardSync.js +35 -23
  29. package/dist/vendored/server/core/cardVersion.js +1 -1
  30. package/dist/vendored/server/core/flameCompile/client.js +466 -0
  31. package/dist/vendored/server/core/flameCompile/clientElements.js +372 -0
  32. package/dist/vendored/server/core/flameCompile/common.js +253 -0
  33. package/dist/vendored/server/core/flameCompile/h5.js +326 -0
  34. package/dist/vendored/server/core/flameCompile/index.js +126 -0
  35. package/dist/vendored/server/core/flameCompile/model.js +162 -0
  36. package/dist/vendored/server/core/index.js +12 -6
  37. package/dist/vendored/server/core/logger.js +1 -1
  38. package/dist/vendored/server/core/remoteCard.js +5 -5
  39. package/dist/vendored/server/core/skillPaths.js +1 -1
  40. package/dist/vendored/server/core/sliceUpload.js +4 -4
  41. package/dist/vendored/skills/README.md +13 -13
  42. package/dist/vendored/skills/card-doctor/SKILL.md +2 -2
  43. package/dist/vendored/skills/dsl-card-ai/SKILL.md +4 -4
  44. package/dist/vendored/skills/dsl-card-generator/SKILL.md +2 -2
  45. package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +1 -1
  46. package/dist/vendored/skills/dsl-card-generator/references/build_protocol.md +54 -0
  47. package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +2 -2
  48. package/dist/vendored/skills/dsl-card-generator/references/converge_protocol.md +36 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +1 -1
  50. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
  51. package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +1 -1
  52. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +1 -1
  53. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +4 -4
  54. package/dist/vendored/skills/dsl-card-generator/references/rules.md +1 -1
  55. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
  56. package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +1 -1
  57. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +1 -1
  58. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +2 -2
  59. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-CeVX_y-M.js +137 -0
  60. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-DiEiY59H.css +1 -0
  61. package/dist/vendored/skills/dsl-card-generator/vendor/preview/standalone.html +2 -2
  62. package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/SKILL.md +26 -26
  63. package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/api_batch_sync.mjs +184 -162
  64. package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/slice_upload.mjs +48 -29
  65. package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/SKILL.md +13 -13
  66. package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package-lock.json +2 -2
  67. package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package.json +2 -2
  68. package/dist/vendored/skills/{finmall-session-navigator/scripts/finmall_login.mjs → platform-session-navigator/scripts/platform_login.mjs} +41 -31
  69. package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/session-manager.mjs +17 -17
  70. package/dist/vendored/skills/shared/README.md +21 -21
  71. package/dist/vendored/skills/shared/auth-model.md +8 -10
  72. package/dist/vendored/skills/shared/{finmall-config.mjs → platform-config.mjs} +10 -10
  73. package/dist/vendored/tools/{finmall-env.mjs → platform-env.mjs} +13 -13
  74. package/package.json +1 -1
  75. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-BCLshIzC.css +0 -1
  76. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-pP5kTKiI.js +0 -88
@@ -1,26 +1,61 @@
1
1
  /**
2
2
  * 远端卡片列表缓存(dev server 进程内单例)。
3
3
  *
4
- * 用途:推送(保存 / 预览)前确认目标卡片确实存在于 FinMall 卡片列表中,
4
+ * 用途:推送(保存 / 预览 / 发布)前确认目标卡片确实存在于远端卡片列表中,
5
5
  * 拦截「推到已删除 / pageId 错位的卡片」这类风险。
6
6
  *
7
7
  * 数据来源:IDC10001 卡片列表(与同步同一份数据)。刷新时机由同步逻辑触发——
8
8
  * 全量同步(无 cardCode 过滤)时整体替换;单卡同步时增量合并,避免清空其他卡片。
9
9
  * 列表条目字段:cardId(数字型,等于 schema.pageId)、code(英文卡 id)、cardName。
10
10
  *
11
- * 单例性:本模块被 server/* 与 skills/finmall-card-sync 以相同绝对路径 import,
11
+ * app 上下文:列表内容随 appId / workspaceId 而异(同一 baseUrl 下多个应用各有
12
+ * 独立卡片列表)。开发中途切换 .env.cb 的 appId 后,旧 app 的缓存对当前环境
13
+ * 完全无意义——若不失效,存在性闸门会拿旧 app 的列表误判新 app 的卡片
14
+ * 「不在远端列表中」。因此:
15
+ * - refreshCardList 写入时盖 appId 戳;发现 appId 变化即清空旧缓存(无论 full 与否);
16
+ * - checkPushAllowed 带上当前 appId 比对,缓存属于另一 app 时视为「未同步」放行。
17
+ *
18
+ * 单例性:本模块被 server/* 与 skills/platform-card-sync 以相同绝对路径 import,
12
19
  * 在同一 dev server 进程内共享同一份内存,故同步写入、推送读取天然一致。
13
20
  */
14
21
 
22
+ import fs from 'node:fs'
23
+ import path from 'node:path'
24
+ import { pathToFileURL } from 'node:url'
25
+ import { skillsCandidates } from './skillPaths.js'
26
+
15
27
  const cardIndex = new Map(); // cardId(String) -> { cardId, code, cardName }
16
28
 
29
+ /** 缓存当前所属的远端环境(由最近一次 refreshCardList 盖戳)。 */
30
+ const cacheCtx = { appId: null, workspaceId: null };
31
+
32
+ /**
33
+ * 最近一次「全量同步」所属的 appId。存在性闸门只在「当前 appId == 最近全量同步的
34
+ * appId」时才有判离权:增量合并(单卡同步)只会让列表更片面,片面列表永远不能
35
+ * 证明「卡片不在远端」——拿它做存在性判断就是刻舟求剑。
36
+ */
37
+ let fullSyncedAppId = null;
38
+
17
39
  /**
18
40
  * 写入/合并卡片列表。
19
41
  * @param {Array<{cardId:string|number, code?:string, cardName?:string}>} cards
20
- * @param {{ full?: boolean }} [opts] full=true 时整体替换(先清空),否则增量合并。
42
+ * @param {{ full?: boolean, appId?: string|number, workspaceId?: string|number }} [opts]
43
+ * full=true 时整体替换(先清空),否则增量合并;
44
+ * appId 与缓存戳不一致(环境切换)时无条件清空——旧 app 的列表条目不可复用;
45
+ * 仅 full=true 才更新 fullSyncedAppId(增量合并不赋予闸门判离权)。
21
46
  */
22
- export function refreshCardList(cards, { full = false } = {}) {
23
- if (full) cardIndex.clear();
47
+ export function refreshCardList(cards, { full = false, appId = null, workspaceId = null } = {}) {
48
+ const ctxChanged =
49
+ appId != null && cacheCtx.appId != null && String(cacheCtx.appId) !== String(appId);
50
+ if (full || ctxChanged) {
51
+ cardIndex.clear();
52
+ if (ctxChanged) fullSyncedAppId = null; // 换了环境,旧的全量结论作废
53
+ }
54
+ if (appId != null) {
55
+ cacheCtx.appId = String(appId);
56
+ cacheCtx.workspaceId = workspaceId == null ? null : String(workspaceId);
57
+ }
58
+ if (full && appId != null) fullSyncedAppId = String(appId);
24
59
  for (const c of cards || []) {
25
60
  if (c == null || c.cardId == null) continue;
26
61
  const id = String(c.cardId);
@@ -35,17 +70,64 @@ export function hasCardId(cardId) {
35
70
 
36
71
  /**
37
72
  * 推送前存在性校验:目标卡片是否确实在远端卡片列表中(按 cardId/pageId 查)。
38
- * - 缓存为空(本次 dev server 尚未全量同步过):放行,返回 allowed=true,避免误拦截首推。
39
- * - 命中:allowed=true。
40
- * - 未命中:allowed=false,reason 给出拦截说明(疑似已删除 / pageId 错位)。
73
+ * - 缓存为空(本次 dev server 尚未同步过):放行,避免误拦截首推。
74
+ * - 当前 appId 从未做过全量同步(或中途切换过环境):缓存片面 / 不可判,放行并标记
75
+ * notFullSynced / staleContext=true(调用方可据此记日志提示)。
76
+ * - 命中:放行。
77
+ * - 未命中(且当前 appId 有全量列表背书):拒绝,reason 给出拦截说明。
41
78
  * @param {string|number} pageId
42
- * @returns {{ allowed: boolean, reason: string }}
79
+ * @param {{ appId?: string|number }} [ctx] 当前生效的远端环境(来自 platform-config)
80
+ * @returns {{ allowed: boolean, reason: string, staleContext?: boolean, notFullSynced?: boolean }}
43
81
  */
44
- export function checkPushAllowed(pageId) {
82
+ export function checkPushAllowed(pageId, { appId = null } = {}) {
45
83
  if (cardIndex.size === 0) return { allowed: true, reason: '' };
84
+ if (appId != null && String(appId) !== fullSyncedAppId) {
85
+ const staleContext =
86
+ cacheCtx.appId != null && String(cacheCtx.appId) !== String(appId);
87
+ return {
88
+ allowed: true,
89
+ reason: '',
90
+ staleContext: staleContext || undefined,
91
+ notFullSynced: !staleContext || undefined,
92
+ };
93
+ }
46
94
  if (hasCardId(pageId)) return { allowed: true, reason: '' };
47
95
  return {
48
96
  allowed: false,
49
97
  reason: `卡片 pageId=${pageId} 不在远端卡片列表中,疑似已删除或 pageId 错位,请先「远程同步」刷新列表后再推送`,
50
98
  };
51
99
  }
100
+
101
+ /**
102
+ * 读取当前工程生效的远端环境(appId / workspaceId),供推送闸门做上下文比对。
103
+ * 从项目 skills/shared/platform-config.mjs 动态加载(三态解析,优先项目根下的
104
+ * skills/——其内部按自身位置向上找 .env.cb,读到的正是当前工程生效值)。
105
+ * 进程内按 root 记忆化:配置在 dev server 生命周期内视为不变(改 .env 需重启服务)。
106
+ * @param {string} root 项目根
107
+ * @returns {Promise<{ appId: string|null, workspaceId: string|null }>}
108
+ */
109
+ const _ctxPromises = new Map();
110
+ export function currentAppContext(root) {
111
+ const key = String(root || '');
112
+ if (!_ctxPromises.has(key)) {
113
+ _ctxPromises.set(
114
+ key,
115
+ (async () => {
116
+ for (const dir of skillsCandidates(root)) {
117
+ const p = path.join(dir, 'shared', 'platform-config.mjs');
118
+ if (!fs.existsSync(p)) continue;
119
+ try {
120
+ const m = await import(pathToFileURL(p).href);
121
+ const c = m.platformConfig || {};
122
+ return {
123
+ appId: c.appId != null ? String(c.appId) : null,
124
+ workspaceId: c.workspaceId != null ? String(c.workspaceId) : null,
125
+ };
126
+ } catch { /* 读下一个候选 */ }
127
+ }
128
+ return { appId: null, workspaceId: null };
129
+ })(),
130
+ );
131
+ }
132
+ return _ctxPromises.get(key);
133
+ }
@@ -2,23 +2,23 @@ import path from 'node:path'
2
2
  import { pathToFileURL } from 'node:url'
3
3
  import { assertRemoteSource, findCardDir } from './cardSidecar.js'
4
4
  import { commitCardChanges } from './cardGitCommit.js'
5
- import { checkPushAllowed } from './cardListCache.js'
5
+ import { checkPushAllowed, currentAppContext } from './cardListCache.js'
6
6
  import { log } from './logger.js'
7
7
  import { resolveSkillPathOrProject } from './skillPaths.js'
8
8
 
9
9
  /**
10
- * finmall-card-sync 的批量同步脚本(发布复用),相对 skills/
10
+ * platform-card-sync 的批量同步脚本(发布复用),相对 skills/
11
11
  * 与 cardSync.js 同款定位:发布只用到其中的 publishCard。
12
12
  */
13
- const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
13
+ const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
14
14
 
15
15
  /**
16
16
  * 顶栏「远端 → 卡片发布」的服务端处理:取远端最大版本号 +1 后发布上线(IDC10004 → IDC10016)。
17
- * 复用 finmall-card-sync 的 publishCard(与「同步」「保存远端」「预览」共用同一后台 FinMall 会话)。
17
+ * 复用 platform-card-sync 的 publishCard(与「同步」「保存远端」「预览」共用同一后台远端会话)。
18
18
  *
19
19
  * 安全闸(与 /__preview 一致):
20
20
  * - assertRemoteSource:本地自建、无 .import-meta.json 侧车的卡一律禁止发布,
21
- * 避免把测试数据误发布到 FinMall 服务器。
21
+ * 避免把测试数据误发布到远端服务器。
22
22
  * - checkPushAllowed:卡片须存在于远端列表缓存(全量同步时刷新);缓存为空(尚未同步)则放行。
23
23
  *
24
24
  * 发布成功后的收尾:把该卡目录(src/cards/<code>/)的未提交变更提交为一条
@@ -64,8 +64,13 @@ export function createPublishMiddleware({ root }) {
64
64
  }
65
65
  }
66
66
  // 存在性闸门:发布前确认目标卡片在远端卡片列表缓存中(为空说明尚未同步,放行)。
67
+ // 缓存带 appId 戳:环境切换后旧 app 的缓存自动失效放行,不再误判「不在远端列表」。
67
68
  {
68
- const allow = checkPushAllowed(payload.schema.pageId)
69
+ const ctx = await currentAppContext(root)
70
+ const allow = checkPushAllowed(payload.schema.pageId, ctx)
71
+ if (allow.staleContext || allow.notFullSynced) {
72
+ log.warn('远端列表缓存对当前 appId 不可判(环境已切换或未全量同步),存在性闸门放行;建议先「远程同步」刷新当前环境列表', { ...ctx, staleContext: !!allow.staleContext })
73
+ }
69
74
  if (!allow.allowed) {
70
75
  res.statusCode = 409
71
76
  res.setHeader('Content-Type', 'application/json')
@@ -4,7 +4,7 @@ import { readImportMeta } from './cardImportMeta.js'
4
4
  import { log } from './logger.js'
5
5
  import { resolveSkillPathOrProject } from './skillPaths.js'
6
6
 
7
- const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
7
+ const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
8
8
 
9
9
  /**
10
10
  * 调试面板「导入信息」tab「发布版本记录」弹框的远端数据源:
@@ -248,13 +248,13 @@ function handleStopPublish({ root, cardCode, req, res }) {
248
248
  message: raw,
249
249
  })
250
250
  }
251
- // finmallPost 抛出的是「FinMall 交易失败 [errorCode=IMVM0005] 状态修改失败 | {整包 JSON}」,
251
+ // platformPost 抛出的是「远端交易失败 [errorCode=IMVM0005] 状态修改失败 | {整包 JSON}」,
252
252
  // 整包塞进 toast 读不过来:这里只取 errorMsg + code,其余留日志。
253
253
  log.error('停止远端发布失败', { cardCode, versionId, taskId, error: raw.slice(0, 300) })
254
254
  sendJson(res, 500, {
255
255
  ok: false,
256
256
  error: 'stop_publish_failed',
257
- message: finmallErrorMessage(raw, '远端拒绝了本次停止操作'),
257
+ message: platformErrorMessage(raw, '远端拒绝了本次停止操作'),
258
258
  })
259
259
  }
260
260
  })()
@@ -368,7 +368,7 @@ function handlePublishVersion({ root, cardCode, req, res }) {
368
368
  sendJson(res, 500, {
369
369
  ok: false,
370
370
  error: 'publish_failed',
371
- message: finmallErrorMessage(raw, '远端拒绝了本次发布'),
371
+ message: platformErrorMessage(raw, '远端拒绝了本次发布'),
372
372
  })
373
373
  }
374
374
  })()
@@ -376,12 +376,12 @@ function handlePublishVersion({ root, cardCode, req, res }) {
376
376
  }
377
377
 
378
378
  /**
379
- * 把 finmallPost 抛出的原始错误压成一句可读文案。
380
- * 原始形态:「FinMall 交易失败 [errorCode=IMVM0005] 状态修改失败 | {整包 JSON}」——
379
+ * 把 platformPost 抛出的原始错误压成一句可读文案。
380
+ * 原始形态:「远端交易失败 [errorCode=IMVM0005] 状态修改失败 | {整包 JSON}」——
381
381
  * 整包 JSON 塞进 toast 读不过来,只取 errorMsg + code,其余由调用方写日志。
382
382
  */
383
- function finmallErrorMessage(raw, fallback) {
384
- const m = String(raw || '').match(/FinMall 交易失败 \[errorCode=([^\]]+)\]\s*([^|]*)/)
383
+ function platformErrorMessage(raw, fallback) {
384
+ const m = String(raw || '').match(/远端交易失败 \[errorCode=([^\]]+)\]\s*([^|]*)/)
385
385
  if (!m) return raw
386
386
  return `${(m[2] || '').trim() || fallback}(${m[1]})`
387
387
  }
@@ -5,15 +5,15 @@ import { fileURLToPath } from 'node:url'
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
6
 
7
7
  import { recordExport, markDirty } from './cardImportMeta.js'
8
- import { checkPushAllowed } from './cardListCache.js'
8
+ import { checkPushAllowed, currentAppContext } from './cardListCache.js'
9
9
  import { assertRemoteSource, findCardDir } from './cardSidecar.js'
10
10
  import { log } from './logger.js'
11
11
  import { resolveSkillPathOrProject } from './skillPaths.js'
12
12
  import { generateCardCover } from './screenshot.js'
13
13
  import { serialize } from './browser.js'
14
14
 
15
- /** 远端保存脚本相对 skills/ 的路径(与「同步」共用 finmall-card-sync) */
16
- const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
15
+ /** 远端保存脚本相对 skills/ 的路径(与「同步」共用 platform-card-sync) */
16
+ const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
17
17
 
18
18
  /**
19
19
  * 推送成功后「截图 + 上传为远端预览图」的整段预算。
@@ -47,7 +47,7 @@ function withDeadline(promise, ms, label) {
47
47
  *
48
48
  * 为什么放在服务端而不是前端:①截图走本地无头渲染(screenshot.js 单例 Chrome),
49
49
  * 与「预览图」按钮同一条链路,产出透明底、与卡片实际渲染尺寸 1:1 的 PNG;
50
- * ②上传必须带 FinMall token,只有已登录的后台会话拿得到,前端不接触凭据。
50
+ * ②上传必须带远端 token,只有已登录的后台会话拿得到,前端不接触凭据。
51
51
  *
52
52
  * 三条纪律:
53
53
  * - **best-effort**:任何失败都返回结构化结果,绝不向上抛——预览图是推送的附加产物,
@@ -108,14 +108,14 @@ export async function uploadPreviewAfterPush({
108
108
  }
109
109
 
110
110
  /**
111
- * 远端保存(写回 FinMall)服务端处理。
111
+ * 远端保存(写回远端)服务端处理。
112
112
  * 前端「保存」按钮把当前预览的 schema(含实时数据绑定)POST 过来,
113
113
  * 本中间件校验 pageId 后,经已登录浏览器中转调用 IDC10010.do 写回远端,
114
- * 与「同步」共用 finmall-card-sync 的 saveCardToRemote(带会话自愈)。
114
+ * 与「同步」共用 platform-card-sync 的 saveCardToRemote(带会话自愈)。
115
115
  *
116
116
  * @param {{ root: string, previewAfterPush?: typeof uploadPreviewAfterPush }} opts
117
117
  * - previewAfterPush:推送成功后的「截图 + 上传预览图」实现,默认即上面的
118
- * uploadPreviewAfterPush;单测注入替身以便在无 Chrome / 无 FinMall 会话的环境断言接线。
118
+ * uploadPreviewAfterPush;单测注入替身以便在无 Chrome / 无远端会话的环境断言接线。
119
119
  */
120
120
  export function createSaveMiddleware({ root, previewAfterPush = uploadPreviewAfterPush }) {
121
121
  return async function saveMiddleware(req, res, next) {
@@ -150,7 +150,7 @@ export function createSaveMiddleware({ root, previewAfterPush = uploadPreviewAft
150
150
  return res.end(JSON.stringify({ ok: false, error: 'schema.pageId 缺失,无法确定要保存的卡片' }))
151
151
  }
152
152
  // 远端来源硬闸:本地自建、无 .import-meta.json 侧车的卡一律禁止推送,
153
- // 避免把测试数据误提交到 FinMall 服务器(比「是否在远端列表」更根本)。
153
+ // 避免把测试数据误提交到远端服务器(比「是否在远端列表」更根本)。
154
154
  {
155
155
  const src = assertRemoteSource(root, { code, pageId: schema.pageId })
156
156
  if (!src.ok) {
@@ -183,9 +183,13 @@ export function createSaveMiddleware({ root, previewAfterPush = uploadPreviewAft
183
183
  }
184
184
  }
185
185
  // 存在性闸门:推送前确认目标卡片在远端卡片列表缓存中(全量同步时刷新)。
186
- // 缓存非空才校验(为空说明尚未同步,放行);不存在则拒绝推送,防打错/已删卡片。
186
+ // 缓存非空才校验(为空说明尚未同步,放行);缓存属于其他 appId(环境切换)也放行。
187
187
  {
188
- const allow = checkPushAllowed(schema.pageId)
188
+ const ctx = await currentAppContext(root)
189
+ const allow = checkPushAllowed(schema.pageId, ctx)
190
+ if (allow.staleContext || allow.notFullSynced) {
191
+ log.warn('远端列表缓存对当前 appId 不可判(环境已切换或未全量同步),存在性闸门放行;建议先「远程同步」刷新当前环境列表', { ...ctx, staleContext: !!allow.staleContext })
192
+ }
189
193
  if (!allow.allowed) {
190
194
  res.statusCode = 409
191
195
  res.setHeader('Content-Type', 'application/json')
@@ -2,9 +2,9 @@
2
2
  * 卡片「远端来源侧车」判定。
3
3
  *
4
4
  * 本地卡片目录 src/cards/<code>/ 下的 .import-meta.json 由「远程同步」写入,
5
- * 记录该卡来自 FinMall 远端的 cardId 等来源信息。只有存在侧车的卡才被视为
5
+ * 记录该卡来自远端的 cardId 等来源信息。只有存在侧车的卡才被视为
6
6
  * 「有合法远端来源」,允许推送(保存 / 预览)到服务器;本地自建、从未同步过的
7
- * 测试卡没有侧车,一律禁止推送,避免把测试数据误提交到 FinMall。
7
+ * 测试卡没有侧车,一律禁止推送,避免把测试数据误提交到远端。
8
8
  */
9
9
 
10
10
  import fs from 'node:fs'
@@ -66,7 +66,7 @@ export function assertRemoteSource(root, { code, pageId } = {}) {
66
66
  ok: false,
67
67
  reason:
68
68
  '该卡片为本地自建、无远端同步来源(缺少 .import-meta.json 侧车)。' +
69
- '为防止将测试数据误提交到 FinMall 服务器,已禁止保存 / 推送。' +
69
+ '为防止将测试数据误提交到远端服务器,已禁止保存 / 推送。' +
70
70
  '请先通过「远程同步」把卡片来源刷新到本地后再操作。',
71
71
  }
72
72
  }
@@ -1,14 +1,14 @@
1
1
  import path from 'node:path'
2
2
  import { pathToFileURL } from 'node:url'
3
3
  import { createHash } from 'node:crypto'
4
- import { checkPushAllowed, refreshCardList, hasCardId } from './cardListCache.js'
4
+ import { checkPushAllowed, refreshCardList, hasCardId, currentAppContext } from './cardListCache.js'
5
5
  import { assertRemoteSource } from './cardSidecar.js'
6
6
  import { log, fmtBytes } from './logger.js'
7
7
  import { buildImportMeta, writeImportMeta } from './cardImportMeta.js'
8
8
  import { resolveSkillPathOrProject, projectSkillsDir } from './skillPaths.js'
9
9
 
10
- /** finmall-card-sync 的批量同步脚本(同步 / 远端预览共用),相对 skills/ */
11
- const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
10
+ /** platform-card-sync 的批量同步脚本(同步 / 远端预览共用),相对 skills/ */
11
+ const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
12
12
 
13
13
  /** 卡片 id 合法形态(与 normalizeCard / 新建卡同源正则) */
14
14
  const CARD_CODE_RE = /^[A-Za-z0-9_-]+$/
@@ -96,8 +96,8 @@ function logSyncSummary(summary, requestLabel) {
96
96
  }
97
97
 
98
98
  /**
99
- * 预览「同步远程数据」按钮的服务端处理:经已认证的 FinMall 浏览器会话拉取最新卡片写回 src/cards。
100
- * 复用 finmall-card-sync 的 syncCards(connectOverCDP 连用户已开的 Chrome,disconnect 不杀进程)。
99
+ * 预览「同步远程数据」按钮的服务端处理:经已认证的远端浏览器会话拉取最新卡片写回 src/cards。
100
+ * 复用 platform-card-sync 的 syncCards(connectOverCDP 连用户已开的 Chrome,disconnect 不杀进程)。
101
101
  *
102
102
  * 任务制端点(编辑器「远端 → 查看全部 → 下载选中 / 全部下载」的进度弹窗依赖):
103
103
  * - POST /__sync body { cardCode? , cardCodes? } → 立即返回 { ok, jobId, total }
@@ -282,7 +282,7 @@ export function createSyncMiddleware({ root }) {
282
282
  job.result = {
283
283
  success: false,
284
284
  timedOut: true,
285
- error: `同步任务超过 ${Math.round(SYNC_JOB_TIMEOUT_MS / 1000)} 秒未完成,已自动收尾(上游无响应)。请重试,或检查 FinMall 登录态 / 网络。`,
285
+ error: `同步任务超过 ${Math.round(SYNC_JOB_TIMEOUT_MS / 1000)} 秒未完成,已自动收尾(上游无响应)。请重试,或检查远端登录态 / 网络。`,
286
286
  }
287
287
  log.error('远端同步看门狗触发:任务超时,已代其收尾并释放同步锁', {
288
288
  jobId: job.id,
@@ -352,9 +352,9 @@ export function createSyncMiddleware({ root }) {
352
352
 
353
353
  /**
354
354
  * 预览「远端预览」按钮的服务端处理:复刻 ubomp 的 PreviewPopover 全链路——
355
- * 先把当前卡片保存到 FinMall(IDC10010),再取卡片 code(IDC10015)、
355
+ * 先把当前卡片保存到远端(IDC10010),再取卡片 code(IDC10015)、
356
356
  * 发布预览测试版(IDC10020)、通知真机(IDC10021),最终返回 cardCode 供前端生成二维码。
357
- * 复用 finmall-card-sync 的 previewCardRemote(与「同步」「保存远端」共用同一后台 FinMall 会话)。
357
+ * 复用 platform-card-sync 的 previewCardRemote(与「同步」「保存远端」共用同一后台远端会话)。
358
358
  *
359
359
  * @param {{ root: string }} opts
360
360
  * @returns {(req: import('http').IncomingMessage, res: import('http').ServerResponse, next: Function) => void}
@@ -386,7 +386,7 @@ export function createPreviewMiddleware({ root }) {
386
386
  return res.end(JSON.stringify({ error: 'missing schema.pageId', message: '需传入当前卡片 schema(含 pageId)' }))
387
387
  }
388
388
  // 远端来源硬闸:本地自建、无 .import-meta.json 侧车的卡一律禁止推送,
389
- // 避免把测试数据误提交到 FinMall 服务器(比「是否在远端列表」更根本)。
389
+ // 避免把测试数据误提交到远端服务器(比「是否在远端列表」更根本)。
390
390
  {
391
391
  const src = assertRemoteSource(root, { pageId: payload.schema.pageId })
392
392
  if (!src.ok) {
@@ -396,9 +396,13 @@ export function createPreviewMiddleware({ root }) {
396
396
  }
397
397
  }
398
398
  // 存在性闸门:推送前确认目标卡片在远端卡片列表缓存中(全量同步时刷新)。
399
- // 缓存非空才校验(为空说明尚未同步,放行);不存在则拒绝推送,防打错/已删卡片。
399
+ // 缓存非空才校验(为空说明尚未同步,放行);缓存属于其他 appId(环境切换)也放行。
400
400
  {
401
- const allow = checkPushAllowed(payload.schema.pageId)
401
+ const ctx = await currentAppContext(root)
402
+ const allow = checkPushAllowed(payload.schema.pageId, ctx)
403
+ if (allow.staleContext || allow.notFullSynced) {
404
+ log.warn('远端列表缓存对当前 appId 不可判(环境已切换或未全量同步),存在性闸门放行;建议先「远程同步」刷新当前环境列表', { ...ctx, staleContext: !!allow.staleContext })
405
+ }
402
406
  if (!allow.allowed) {
403
407
  res.statusCode = 409
404
408
  res.setHeader('Content-Type', 'application/json')
@@ -428,22 +432,22 @@ export function createPreviewMiddleware({ root }) {
428
432
  }
429
433
  }
430
434
 
431
- /** finmall-session-navigator 登录脚本(凭据读写 + 页面内登录验证),相对 skills/ */
432
- const SESSION_LOGIN_REL = 'finmall-session-navigator/scripts/finmall_login.mjs'
433
- /** finmall-session-navigator 后台会话管理(同步 / 登录验证共用同一 headless Chrome) */
434
- const SESSION_MANAGER_REL = 'finmall-session-navigator/scripts/session-manager.mjs'
435
+ /** platform-session-navigator 登录脚本(凭据读写 + 页面内登录验证),相对 skills/ */
436
+ const SESSION_LOGIN_REL = 'platform-session-navigator/scripts/platform_login.mjs'
437
+ /** platform-session-navigator 后台会话管理(同步 / 登录验证共用同一 headless Chrome) */
438
+ const SESSION_MANAGER_REL = 'platform-session-navigator/scripts/session-manager.mjs'
435
439
 
436
- /** 凭据落盘路径:与 finmall_login.mjs 默认路径同址,但经 projectSkillsDir 永远写项目根 skills/,不进 node_modules */
440
+ /** 凭据落盘路径:与 platform_login.mjs 默认路径同址,但经 projectSkillsDir 永远写项目根 skills/,不进 node_modules */
437
441
  function credentialsFilePath(root) {
438
442
  return path.join(
439
443
  projectSkillsDir(root),
440
- 'finmall-session-navigator',
444
+ 'platform-session-navigator',
441
445
  '.local',
442
446
  'credentials.json',
443
447
  )
444
448
  }
445
449
 
446
- /** 动态加载 finmall-session-navigator 登录脚本(仓库内 / 消费者工程 / 包内快照三态通用) */
450
+ /** 动态加载 platform-session-navigator 登录脚本(仓库内 / 消费者工程 / 包内快照三态通用) */
447
451
  async function loadLoginModule(root) {
448
452
  const p = resolveSkillPathOrProject(root, SESSION_LOGIN_REL)
449
453
  return import(pathToFileURL(p).href)
@@ -462,14 +466,14 @@ async function readSavedLoginName(root) {
462
466
 
463
467
  /**
464
468
  * 用给定账号密码做一次真实登录验证(不落盘)。
465
- * 与「同步」共用同一后台 headless Chrome 会话池:连上 / 拉起后在 FinMall 同源页面内
469
+ * 与「同步」共用同一后台 headless Chrome 会话池:连上 / 拉起后在远端同源页面内
466
470
  * 直调登录接口(performLogin),以是否建立会话(sessionCreated)判定凭据是否正确——
467
471
  * 已登录会话不会蒙混过关,因为performLogin 每次都真实提交一遍账号密码。
468
472
  * 只 disconnect 不关闭浏览器:后台 Chrome 以 detached 常驻,供后续同步复用。
469
473
  *
470
474
  * @returns {Promise<{ sessionCreated: boolean, businessFailure: boolean, status?: number }>}
471
475
  */
472
- async function verifyFinmallLogin(root, loginName, rawPassword) {
476
+ async function verifyPlatformLogin(root, loginName, rawPassword) {
473
477
  const {
474
478
  performLogin,
475
479
  tryConnectBrowser,
@@ -485,10 +489,18 @@ async function verifyFinmallLogin(root, loginName, rawPassword) {
485
489
  const md5Hex = createHash('md5')
486
490
  .update(String(rawPassword), 'utf8')
487
491
  .digest('hex')
492
+ // 对端站点 host:从登录模块给出的目标 URL 派生(不写死域名),用于认出已登录的标签页
493
+ const targetHost = (() => {
494
+ try {
495
+ return new URL(TARGET_URL).host
496
+ } catch {
497
+ return ''
498
+ }
499
+ })()
488
500
 
489
501
  let browser = await tryConnectBrowser(SYNC_PORT)
490
502
  if (!browser) {
491
- // 端口未启动:拉起后台 headless Chrome(与 ensureFinMallSession 同端口同数据目录)
503
+ // 端口未启动:拉起后台 headless Chrome(与 ensurePlatformSession 同端口同数据目录)
492
504
  launchChromeDetached(findChromePath(), SYNC_BROWSER_DATA_DIR, SYNC_PORT, TARGET_URL, {
493
505
  headless: true,
494
506
  })
@@ -508,7 +520,7 @@ async function verifyFinmallLogin(root, loginName, rawPassword) {
508
520
  }
509
521
  try {
510
522
  const context = browser.contexts()[0]
511
- let page = context?.pages().find((p) => p.url().includes('finmall.com'))
523
+ let page = context?.pages().find((p) => targetHost && p.url().includes(targetHost))
512
524
  if (!page) {
513
525
  page = context ? await context.newPage() : await browser.newPage()
514
526
  await page
@@ -594,7 +606,7 @@ export function createLoginCredentialsMiddleware({ root }) {
594
606
  }
595
607
  try {
596
608
  // 1) 先做一次真实登录验证,通过才落盘
597
- const result = await verifyFinmallLogin(root, loginName, rawPassword)
609
+ const result = await verifyPlatformLogin(root, loginName, rawPassword)
598
610
  if (result.sessionCreated) {
599
611
  const { saveCredentials } = await loadLoginModule(root)
600
612
  await saveCredentials(loginName, rawPassword, credentialsFilePath(root))
@@ -5,7 +5,7 @@ import { readImportMeta, recordRemoteVersion } from './cardImportMeta.js'
5
5
  import { log } from './logger.js'
6
6
  import { resolveSkillPathOrProject } from './skillPaths.js'
7
7
 
8
- const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
8
+ const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
9
9
 
10
10
  /**
11
11
  * 调试面板「卡片来源」tab 的远端最新版本持久化(POST,fetch + persist):POST /__card-version/:id