cardbuddy 0.18.0 → 0.19.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/dist/preview-editor/assets/index-DMqeReNL.css +1 -0
- package/dist/preview-editor/assets/{index-kaTaTm71.js → index-rRajzE-f.js} +74 -70
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/server/core/cardImportMeta.js +19 -0
- package/dist/vendored/server/core/cardPublish.js +100 -0
- package/dist/vendored/server/core/cardSave.js +8 -3
- package/dist/vendored/server/core/cardVersion.js +93 -0
- package/dist/vendored/server/core/index.js +10 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +101 -0
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-DPK_SisH.css +0 -1
|
@@ -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-rRajzE-f.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DMqeReNL.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="app"></div>
|
|
@@ -127,6 +127,25 @@ export function recordExport(dir, verified) {
|
|
|
127
127
|
return next
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* 记录一次远端最新版本号(「卡片来源」tab 展示用,并由发布/拉取/拉取全部后持久化)。
|
|
132
|
+
* 与 recordExport 正交:recordExport 记录本地保存读回校验,本函数记录线上当前最高版本(IDC10004 maxVersion)。
|
|
133
|
+
* 无侧车(本地新建卡)时静默跳过。
|
|
134
|
+
* @param {string} dir 卡片目录
|
|
135
|
+
* @param {string|number|null} version 远端最新版本号;null 表示清除
|
|
136
|
+
*/
|
|
137
|
+
export function recordRemoteVersion(dir, version) {
|
|
138
|
+
const meta = readImportMeta(dir)
|
|
139
|
+
if (!meta) return null
|
|
140
|
+
const next = {
|
|
141
|
+
...meta,
|
|
142
|
+
remoteVersion: version == null ? null : String(version),
|
|
143
|
+
remoteVersionAt: new Date().toISOString(),
|
|
144
|
+
}
|
|
145
|
+
writeImportMeta(dir, next)
|
|
146
|
+
return next
|
|
147
|
+
}
|
|
148
|
+
|
|
130
149
|
/**
|
|
131
150
|
* dev server 中间件:GET /__import-meta/:id 暴露导入来源元数据(供 UI「来源」tab / AI skill 读取)。
|
|
132
151
|
* 纯只读、no-store。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { pathToFileURL } from 'node:url'
|
|
3
|
+
import { assertRemoteSource } from './cardSidecar.js'
|
|
4
|
+
import { checkPushAllowed } from './cardListCache.js'
|
|
5
|
+
import { log } from './logger.js'
|
|
6
|
+
import { resolveSkillPathOrProject } from './skillPaths.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* finmall-card-sync 的批量同步脚本(发布复用),相对 skills/
|
|
10
|
+
* 与 cardSync.js 同款定位:发布只用到其中的 publishCard。
|
|
11
|
+
*/
|
|
12
|
+
const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 顶栏「远端 → 卡片发布」的服务端处理:取远端最大版本号 +1 后发布上线(IDC10004 → IDC10016)。
|
|
16
|
+
* 复用 finmall-card-sync 的 publishCard(与「同步」「保存远端」「预览」共用同一后台 FinMall 会话)。
|
|
17
|
+
*
|
|
18
|
+
* 安全闸(与 /__preview 一致):
|
|
19
|
+
* - assertRemoteSource:本地自建、无 .import-meta.json 侧车的卡一律禁止发布,
|
|
20
|
+
* 避免把测试数据误发布到 FinMall 服务器。
|
|
21
|
+
* - checkPushAllowed:卡片须存在于远端列表缓存(全量同步时刷新);缓存为空(尚未同步)则放行。
|
|
22
|
+
*
|
|
23
|
+
* @param {{ root: string }} opts
|
|
24
|
+
* @returns {(req: import('http').IncomingMessage, res: import('http').ServerResponse, next: Function) => void}
|
|
25
|
+
*/
|
|
26
|
+
export function createPublishMiddleware({ root }) {
|
|
27
|
+
return function publishMiddleware(req, res, next) {
|
|
28
|
+
const pathname = decodeURIComponent((req.url || '').split('?')[0])
|
|
29
|
+
if (pathname !== '/__publish') return next()
|
|
30
|
+
if ((req.method || 'GET').toUpperCase() !== 'POST') return next()
|
|
31
|
+
|
|
32
|
+
let body = ''
|
|
33
|
+
req.setEncoding('utf8')
|
|
34
|
+
req.on('data', (c) => {
|
|
35
|
+
body += c
|
|
36
|
+
if (body.length > 4 * 1024 * 1024) req.destroy()
|
|
37
|
+
})
|
|
38
|
+
req.on('end', async () => {
|
|
39
|
+
let payload
|
|
40
|
+
try {
|
|
41
|
+
payload = JSON.parse(body || '{}')
|
|
42
|
+
} catch (e) {
|
|
43
|
+
res.statusCode = 400
|
|
44
|
+
res.setHeader('Content-Type', 'application/json')
|
|
45
|
+
return res.end(JSON.stringify({ error: 'invalid json', message: String(e.message) }))
|
|
46
|
+
}
|
|
47
|
+
if (!payload.schema || !payload.schema.pageId) {
|
|
48
|
+
res.statusCode = 400
|
|
49
|
+
res.setHeader('Content-Type', 'application/json')
|
|
50
|
+
return res.end(JSON.stringify({ error: 'missing schema.pageId', message: '需传入当前卡片 schema(含 pageId)' }))
|
|
51
|
+
}
|
|
52
|
+
// 远端来源硬闸:本地自建卡禁止发布(与 /__preview 同源)。
|
|
53
|
+
{
|
|
54
|
+
const src = assertRemoteSource(root, { pageId: payload.schema.pageId })
|
|
55
|
+
if (!src.ok) {
|
|
56
|
+
res.statusCode = 409
|
|
57
|
+
res.setHeader('Content-Type', 'application/json')
|
|
58
|
+
return res.end(JSON.stringify({ ok: false, error: 'local_card_no_source', message: src.reason }))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 存在性闸门:发布前确认目标卡片在远端卡片列表缓存中(为空说明尚未同步,放行)。
|
|
62
|
+
{
|
|
63
|
+
const allow = checkPushAllowed(payload.schema.pageId)
|
|
64
|
+
if (!allow.allowed) {
|
|
65
|
+
res.statusCode = 409
|
|
66
|
+
res.setHeader('Content-Type', 'application/json')
|
|
67
|
+
return res.end(JSON.stringify({ ok: false, error: 'card_not_in_list', message: allow.reason }))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const skillPath = resolveSkillPathOrProject(root, SYNC_SCRIPT_REL)
|
|
72
|
+
const { publishCard } = await import(pathToFileURL(skillPath).href)
|
|
73
|
+
const result = await publishCard(payload.schema)
|
|
74
|
+
log.ok('卡片发布完成', { cardId: payload.schema.pageId, nextVersion: result.nextVersion })
|
|
75
|
+
res.statusCode = 200
|
|
76
|
+
res.setHeader('Content-Type', 'application/json')
|
|
77
|
+
return res.end(JSON.stringify({ ok: true, maxVersion: result.maxVersion, nextVersion: result.nextVersion }))
|
|
78
|
+
} catch (e) {
|
|
79
|
+
log.error('卡片发布失败', e)
|
|
80
|
+
// 区分「凭据缺失」与「真正的发布错误」:前者让前端弹登录表单,而非裸抛 ENOENT。
|
|
81
|
+
const isNoCred =
|
|
82
|
+
e &&
|
|
83
|
+
/credentials\.json|credentials 不存在|NO_CREDENTIALS/.test(String(e && e.message))
|
|
84
|
+
res.statusCode = isNoCred ? 200 : 500
|
|
85
|
+
res.setHeader('Content-Type', 'application/json')
|
|
86
|
+
return res.end(
|
|
87
|
+
JSON.stringify(
|
|
88
|
+
isNoCred
|
|
89
|
+
? {
|
|
90
|
+
error: 'need_credentials',
|
|
91
|
+
needCredentials: true,
|
|
92
|
+
message: String(e && e.message),
|
|
93
|
+
}
|
|
94
|
+
: { error: 'publish failed', message: String(e && e.message) },
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
6
|
|
|
7
|
-
import { recordExport } from './cardImportMeta.js'
|
|
7
|
+
import { recordExport, markDirty } from './cardImportMeta.js'
|
|
8
8
|
import { checkPushAllowed } from './cardListCache.js'
|
|
9
9
|
import { assertRemoteSource, findCardDir } from './cardSidecar.js'
|
|
10
10
|
import { log } from './logger.js'
|
|
@@ -107,8 +107,13 @@ export function createSaveMiddleware({ root }) {
|
|
|
107
107
|
if (root) {
|
|
108
108
|
try {
|
|
109
109
|
const dir = findCardDir(root, { pageId: schema.pageId })
|
|
110
|
-
if (dir)
|
|
111
|
-
|
|
110
|
+
if (dir) {
|
|
111
|
+
await recordExport(dir, verified)
|
|
112
|
+
// 推送成功即与远端对齐:清除「偏离导入镜像」标记(来源 tab 据此提示)。
|
|
113
|
+
// 注:本地磁盘(含编辑)仍可能 ≠ 当初导入镜像,此处语义转为「已与远端同步」,
|
|
114
|
+
// 符合用户预期——推送到远端后不应再被标为「偏离」。
|
|
115
|
+
await markDirty(dir, false)
|
|
116
|
+
} else console.warn(`[__save] 未找到 pageId=${schema.pageId} 对应的本地卡片目录,跳过导出元数据`)
|
|
112
117
|
} catch (e) {
|
|
113
118
|
log.warn('写导出元数据失败(不影响保存结果)', { error: e && e.message })
|
|
114
119
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { pathToFileURL } from 'node:url'
|
|
3
|
+
import { findCardDir } from './cardSidecar.js'
|
|
4
|
+
import { readImportMeta, recordRemoteVersion } from './cardImportMeta.js'
|
|
5
|
+
import { log } from './logger.js'
|
|
6
|
+
import { resolveSkillPathOrProject } from './skillPaths.js'
|
|
7
|
+
|
|
8
|
+
const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 调试面板「卡片来源」tab 的远端最新版本持久化(POST,fetch + persist):POST /__card-version/:id
|
|
12
|
+
*
|
|
13
|
+
* 语义:
|
|
14
|
+
* - body 带 { version }:直接把该版本号写入 .import-meta.json(如发布后已知 nextVersion),不再远端查询。
|
|
15
|
+
* - 否则:实时查询 IDC10004 的 maxVersion(线上当前最高版本),写入 .import-meta.json 后返回。
|
|
16
|
+
*
|
|
17
|
+
* 触发时机由前端控制,不再在打开来源 tab 时自动查询(避免每次切 tab 都拉起后台 Chrome):
|
|
18
|
+
* - 卡片发布(doPublish 成功,带 nextVersion)
|
|
19
|
+
* - 拉取 / 拉取全部(runSync 成功,实时查询)
|
|
20
|
+
* - 用户在来源 tab 点「刷新」(主动触发,实时查询)
|
|
21
|
+
*
|
|
22
|
+
* 安全闸(与 /__publish 同源):仅「已同步导入卡」(有 .import-meta.json 侧车且含 cardId)可写;
|
|
23
|
+
* 本地自建卡 / 未同步卡返回 409 local_card_no_source。
|
|
24
|
+
*
|
|
25
|
+
* @param {{ root: string }} opts
|
|
26
|
+
* @returns {(req: import('http').IncomingMessage, res: import('http').ServerResponse, next: Function) => void}
|
|
27
|
+
*/
|
|
28
|
+
export function createCardVersionMiddleware({ root }) {
|
|
29
|
+
return function cardVersionMiddleware(req, res, next) {
|
|
30
|
+
const pathname = decodeURIComponent((req.url || '').split('?')[0])
|
|
31
|
+
const m = pathname.match(/^\/__card-version\/([^/]+)$/)
|
|
32
|
+
if (!m) return next()
|
|
33
|
+
if ((req.method || 'GET').toUpperCase() !== 'POST') return next()
|
|
34
|
+
|
|
35
|
+
const id = decodeURIComponent(m[1])
|
|
36
|
+
const dir = findCardDir(root, { code: id })
|
|
37
|
+
const meta = dir ? readImportMeta(dir) : null
|
|
38
|
+
if (!meta || !meta.cardId) {
|
|
39
|
+
res.statusCode = 409
|
|
40
|
+
res.setHeader('Content-Type', 'application/json')
|
|
41
|
+
return res.end(
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
ok: false,
|
|
44
|
+
error: 'local_card_no_source',
|
|
45
|
+
message: '该卡片为本地自建、无远端同步来源(缺少 .import-meta.json 侧车),无法记录远端版本。',
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 解析请求体(允许空 / { version })
|
|
51
|
+
let body = ''
|
|
52
|
+
req.setEncoding('utf8')
|
|
53
|
+
req.on('data', (c) => {
|
|
54
|
+
body += c
|
|
55
|
+
if (body.length > 64 * 1024) req.destroy()
|
|
56
|
+
})
|
|
57
|
+
req.on('end', async () => {
|
|
58
|
+
let payload = {}
|
|
59
|
+
try {
|
|
60
|
+
payload = JSON.parse(body || '{}')
|
|
61
|
+
} catch {
|
|
62
|
+
/* 空 / 非法体当作无 version */
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
let maxVersion = payload.version != null ? String(payload.version) : null
|
|
66
|
+
if (maxVersion == null) {
|
|
67
|
+
// 未带版本号:实时查询 IDC10004 的 maxVersion
|
|
68
|
+
const skillPath = resolveSkillPathOrProject(root, SYNC_SCRIPT_REL)
|
|
69
|
+
const { fetchCardLatestVersion } = await import(pathToFileURL(skillPath).href)
|
|
70
|
+
const r = await fetchCardLatestVersion(meta.cardId)
|
|
71
|
+
maxVersion = r.maxVersion
|
|
72
|
+
}
|
|
73
|
+
recordRemoteVersion(dir, maxVersion)
|
|
74
|
+
log.ok('记录远端最新版本', { cardId: meta.cardId, maxVersion })
|
|
75
|
+
res.statusCode = 200
|
|
76
|
+
res.setHeader('Content-Type', 'application/json')
|
|
77
|
+
res.end(JSON.stringify({ ok: true, maxVersion }))
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// 区分「凭据缺失」与「真正的查询错误」:前者让前端提示登录,而非裸抛 ENOENT。
|
|
80
|
+
const isNoCred = e && /credentials\.json|credentials 不存在|NO_CREDENTIALS/.test(String(e && e.message))
|
|
81
|
+
res.statusCode = isNoCred ? 200 : 500
|
|
82
|
+
res.setHeader('Content-Type', 'application/json')
|
|
83
|
+
res.end(
|
|
84
|
+
JSON.stringify(
|
|
85
|
+
isNoCred
|
|
86
|
+
? { error: 'need_credentials', needCredentials: true, message: String(e && e.message) }
|
|
87
|
+
: { error: 'version persist failed', message: String(e && e.message) },
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* 接收 viteServer 参数透传;无 SSR 需求的场景可传 null(这两个端点会返回明确错误)。
|
|
12
12
|
*/
|
|
13
13
|
import { createSyncMiddleware, createPreviewMiddleware, createLoginCredentialsMiddleware } from './cardSync.js'
|
|
14
|
+
import { createPublishMiddleware } from './cardPublish.js'
|
|
15
|
+
import { createCardVersionMiddleware } from './cardVersion.js'
|
|
14
16
|
import { createSliceUploadMiddleware } from './sliceUpload.js'
|
|
15
17
|
import { createCardFilesMiddleware, refreshCardList } from './cardFiles.js'
|
|
16
18
|
import { createSaveMiddleware } from './cardSave.js'
|
|
@@ -42,6 +44,8 @@ export {
|
|
|
42
44
|
createSyncMiddleware,
|
|
43
45
|
createPreviewMiddleware,
|
|
44
46
|
createLoginCredentialsMiddleware,
|
|
47
|
+
createPublishMiddleware,
|
|
48
|
+
createCardVersionMiddleware,
|
|
45
49
|
createSliceUploadMiddleware,
|
|
46
50
|
createCardFilesMiddleware,
|
|
47
51
|
createSaveMiddleware,
|
|
@@ -88,6 +92,12 @@ export function mountCoreMiddlewares(app, { root, viteServer = null, upstream =
|
|
|
88
92
|
app.use(createSyncMiddleware({ root }))
|
|
89
93
|
app.use(createPreviewMiddleware({ root }))
|
|
90
94
|
app.use(createLoginCredentialsMiddleware({ root }))
|
|
95
|
+
// 卡片发布(顶栏「远端 → 卡片发布」):取远端最大版本号 +1 后发布上线(IDC10004 → IDC10016),
|
|
96
|
+
// 复用 finmall-card-sync 的 publishCard,与同步/保存/预览共用同一后台 FinMall 会话。
|
|
97
|
+
app.use(createPublishMiddleware({ root }))
|
|
98
|
+
// 卡片来源 tab 的「远端最新版本」查询(只读):GET /__card-version/:id 取 IDC10004 的 maxVersion,
|
|
99
|
+
// 复用 finmall-card-sync 的 fetchCardLatestVersion,与发布/同步/保存共用同一后台 FinMall 会话。
|
|
100
|
+
app.use(createCardVersionMiddleware({ root }))
|
|
91
101
|
// 切图上传(顶栏「远端 → 切图上传」):把当前卡引用的 src/slices 切图传到 FinMall 图片库,
|
|
92
102
|
// 写 src/slices/manifest.json 映射并把卡片里的 /slices/ 引用改写为 @filestore/ 线上地址。
|
|
93
103
|
// 仅在用户点击时触发,没有任何自动上传路径。
|
|
@@ -403,6 +403,107 @@ export async function previewCardRemote(schema, { skeletonScreen = '', publish =
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/**
|
|
407
|
+
* 远端发布:取当前卡片远端最大版本号 +1,发布上线为新版本。
|
|
408
|
+
* 链路(对齐 ubomp 发布流程,仅两步):
|
|
409
|
+
* 1) IDC10004.do 取当前卡片最大版本号 maxVersion;
|
|
410
|
+
* 2) IDC10016.do 以 maxVersion+1 作为新版本号发布(发布的是远端「当前已保存」的卡片内容——
|
|
411
|
+
* 故发布前应先用「推送当前卡」把本地内容保存到远端,否则会发布陈旧内容)。
|
|
412
|
+
* 会话由 ensureFinMallSession 自动保证;与 saveCardToRemote / syncCards / previewCardRemote
|
|
413
|
+
* 共用同一套已登录后台 Chrome,命中会话/权限失效(BLEC0006 等)时自动清 session 重登重试一次。
|
|
414
|
+
*
|
|
415
|
+
* @param {object} schema 当前卡片 schema(需含 pageId)
|
|
416
|
+
* @returns {Promise<{maxVersion:string, nextVersion:string, response:object}>}
|
|
417
|
+
*/
|
|
418
|
+
export async function publishCard(schema) {
|
|
419
|
+
if (!schema || !schema.pageId) {
|
|
420
|
+
throw new Error('schema 缺少 pageId,无法确定要发布的卡片(IDC10016.do 需要 cardId)。')
|
|
421
|
+
}
|
|
422
|
+
const appId = finmallConfig.appId
|
|
423
|
+
const workspaceId = finmallConfig.workspaceId
|
|
424
|
+
const cardId = String(schema.pageId)
|
|
425
|
+
const session = await ensureFinMallSession()
|
|
426
|
+
const browser = session.browser
|
|
427
|
+
try {
|
|
428
|
+
const context = browser.contexts()[0]
|
|
429
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0]
|
|
430
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。')
|
|
431
|
+
await page.bringToFront()
|
|
432
|
+
|
|
433
|
+
// 1) 取当前最大版本号
|
|
434
|
+
const maxResp = await finmallPostWithHeal(
|
|
435
|
+
page,
|
|
436
|
+
`/ubomp-web/IDC10004.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
437
|
+
{ cardId, pageNo: 1, pageSize: 1, sassAppId: appId, sassWorkspaceId: workspaceId },
|
|
438
|
+
'IDC10004.do',
|
|
439
|
+
)
|
|
440
|
+
const maxVersion = String(maxResp && maxResp.maxVersion != null ? maxResp.maxVersion : '0')
|
|
441
|
+
const maxNum = Number(maxVersion)
|
|
442
|
+
const nextVersion = String(Number.isFinite(maxNum) ? maxNum + 1 : 1)
|
|
443
|
+
log.info('发布前取最大版本', { cardId, maxVersion, nextVersion })
|
|
444
|
+
|
|
445
|
+
// 2) 以 maxVersion+1 发布(发布类型/平台等参数对齐 ubomp 发布弹窗的默认值)
|
|
446
|
+
const response = await finmallPostWithHeal(
|
|
447
|
+
page,
|
|
448
|
+
`/ubomp-web/IDC10016.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
449
|
+
{
|
|
450
|
+
cardId,
|
|
451
|
+
version: nextVersion,
|
|
452
|
+
platformStatus: JSON.stringify(['iOS', 'Android', 'HarmonyOS']),
|
|
453
|
+
publishType: '2',
|
|
454
|
+
publishMode: '1',
|
|
455
|
+
whitelistIds: '',
|
|
456
|
+
realGreyEndtime: '',
|
|
457
|
+
greyNum: '1',
|
|
458
|
+
memo: '',
|
|
459
|
+
greyConfigInfo: '[]',
|
|
460
|
+
sassAppId: appId,
|
|
461
|
+
sassWorkspaceId: workspaceId,
|
|
462
|
+
},
|
|
463
|
+
'IDC10016.do',
|
|
464
|
+
)
|
|
465
|
+
log.ok('卡片发布完成', { cardId, nextVersion })
|
|
466
|
+
return { maxVersion, nextVersion, response }
|
|
467
|
+
} finally {
|
|
468
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 取远端「当前卡片最新版本号」:IDC10004 返回 maxVersion(该卡线上已发布的最高版本)。
|
|
474
|
+
* 供 UI「卡片来源」tab 展示「远端最新版本」,让用户一眼看到线上当前版本。
|
|
475
|
+
* 只读、不发布;与 publishCard 共用同一后台 FinMall 会话(ensureFinMallSession + finmallPostWithHeal)。
|
|
476
|
+
* @param {string|number} cardId
|
|
477
|
+
* @returns {Promise<{ maxVersion: string }>}
|
|
478
|
+
*/
|
|
479
|
+
export async function fetchCardLatestVersion(cardId) {
|
|
480
|
+
if (cardId == null) {
|
|
481
|
+
throw new Error('缺少 cardId,无法查询远端版本(IDC10004.do 需要 cardId)。')
|
|
482
|
+
}
|
|
483
|
+
const appId = finmallConfig.appId
|
|
484
|
+
const workspaceId = finmallConfig.workspaceId
|
|
485
|
+
const id = String(cardId)
|
|
486
|
+
const session = await ensureFinMallSession()
|
|
487
|
+
const browser = session.browser
|
|
488
|
+
try {
|
|
489
|
+
const context = browser.contexts()[0]
|
|
490
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0]
|
|
491
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。')
|
|
492
|
+
await page.bringToFront()
|
|
493
|
+
const maxResp = await finmallPostWithHeal(
|
|
494
|
+
page,
|
|
495
|
+
`/ubomp-web/IDC10004.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
496
|
+
{ cardId: id, pageNo: 1, pageSize: 1, sassAppId: appId, sassWorkspaceId: workspaceId },
|
|
497
|
+
'IDC10004.do',
|
|
498
|
+
)
|
|
499
|
+
const maxVersion = String(maxResp && maxResp.maxVersion != null ? maxResp.maxVersion : '0')
|
|
500
|
+
log.info('查询远端最新版本', { cardId: id, maxVersion })
|
|
501
|
+
return { maxVersion }
|
|
502
|
+
} finally {
|
|
503
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
406
507
|
// —— 保存落库校验辅助 ——
|
|
407
508
|
|
|
408
509
|
/** 稳定序列化:对象键排序、带 id/key/className/name 的数组元素按该键排序,避免服务端字段重排导致指纹误判。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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": {
|