cardbuddy 0.18.0 → 0.19.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/dist/preview-editor/assets/{index-kaTaTm71.js → index-1exc5zRY.js} +75 -71
- package/dist/preview-editor/assets/index-C_uqjKqI.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/card-editor/runtime/card-runtime.js +13 -1
- 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/cardSync.js +9 -1
- 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 +122 -6
- package/dist/vendored/skills/finmall-card-sync/scripts/slice_upload.mjs +8 -6
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-DPK_SisH.css +0 -1
|
@@ -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
|
}
|
|
@@ -2,9 +2,10 @@ import path from 'node:path'
|
|
|
2
2
|
import { pathToFileURL } from 'node:url'
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import { writeFile, mkdir } from 'node:fs/promises'
|
|
5
|
-
import { checkPushAllowed } from './cardListCache.js'
|
|
5
|
+
import { checkPushAllowed, refreshCardList, hasCardId } from './cardListCache.js'
|
|
6
6
|
import { assertRemoteSource } from './cardSidecar.js'
|
|
7
7
|
import { log, fmtBytes } from './logger.js'
|
|
8
|
+
import { buildImportMeta, writeImportMeta } from './cardImportMeta.js'
|
|
8
9
|
import { resolveSkillPathOrProject } from './skillPaths.js'
|
|
9
10
|
|
|
10
11
|
/** finmall-card-sync 的批量同步脚本(同步 / 远端预览共用),相对 skills/ */
|
|
@@ -44,6 +45,13 @@ export function createSyncMiddleware({ root }) {
|
|
|
44
45
|
const summary = await syncCards({
|
|
45
46
|
cardCode: payload.cardCode || undefined,
|
|
46
47
|
outRoot: path.join(root, 'src', 'cards'),
|
|
48
|
+
// 从 server/core 上下文注入 cardImportMeta 实现:消费者工程下 api_batch_sync.mjs 按自身
|
|
49
|
+
// skills 位置推导的 workspace 会指向错误根,动态 import 找不到 cardImportMeta.js。
|
|
50
|
+
importMeta: { buildImportMeta, writeImportMeta },
|
|
51
|
+
// 从 server/core 上下文注入 cardListCache 实现:同 cardImportMeta,消费者工程下按自身 skills
|
|
52
|
+
// 位置推导的 workspace 会指向错误根,动态 import 找不到 cardListCache.js,导致全量同步时无法
|
|
53
|
+
// 刷新进程内单例,推送前「存在性闸门」形同虚设。注入后三态都能正确刷新。
|
|
54
|
+
cardListCache: { refreshCardList, checkPushAllowed, hasCardId },
|
|
47
55
|
})
|
|
48
56
|
// 同步成功后在 dev server 终端打印同步结果(统一美化框)。
|
|
49
57
|
const lines = []
|
|
@@ -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
|
// 仅在用户点击时触发,没有任何自动上传路径。
|
|
@@ -53,7 +53,13 @@ for (const key of ["NO_PROXY", "no_proxy"]) {
|
|
|
53
53
|
// 仅在 5180 经中间件触发同步时本进程即 server 进程,缓存写入/读取天然一致;
|
|
54
54
|
// 独立 CLI 运行时不写 server 缓存(无推送发生在该进程),故惰性加载、失败静默降级。
|
|
55
55
|
let _cardListCache = null;
|
|
56
|
+
// 由 server/core 上下文注入(跨「仓库内 / 包内 vendored / 消费者工程 skills」三态都能正确定位
|
|
57
|
+
// cardListCache.js,刷新 dev server 进程内单例,供推送前「存在性闸门」使用)。缺省时退回按本文件
|
|
58
|
+
// 位置推导的路径动态 import——该 fallback 仅在脚本物理位于 <repo>/skills/ 时正确,消费者工程下
|
|
59
|
+
// workspace 会指向错误根,故优先用注入。
|
|
60
|
+
let _injectedCardListCache = null;
|
|
56
61
|
async function getCardListCache() {
|
|
62
|
+
if (_injectedCardListCache) return _injectedCardListCache;
|
|
57
63
|
if (_cardListCache) return _cardListCache;
|
|
58
64
|
try {
|
|
59
65
|
const cachePath = path.resolve(workspace, 'server', 'core', 'cardListCache.js');
|
|
@@ -129,7 +135,15 @@ async function finmallPostWithHeal(page, urlPath, requestBody, transCode = urlPa
|
|
|
129
135
|
* @returns {Promise<{success:boolean, synced:Array, failed:Array, duplicates:Array}>}
|
|
130
136
|
*/
|
|
131
137
|
export async function syncCards(opts = {}) {
|
|
132
|
-
|
|
138
|
+
// 非注入形态(纯 CLI)用 cwd 作为项目根,跨「仓库内 / 消费者工程」一致;server 中间件始终注入绝对 outRoot。
|
|
139
|
+
const outRoot = path.resolve(opts.outRoot || path.join(process.cwd(), 'src', 'cards'));
|
|
140
|
+
// 调用方可注入 cardImportMeta 实现(server/core 上下文,跨「仓库内 / 包内 vendored /
|
|
141
|
+
// 消费者工程 skills」三态都能正确定位 cardImportMeta.js)。缺省时退回按本文件位置推导的路径动态 import,
|
|
142
|
+
// 该 fallback 仅在脚本物理位于 <repo>/skills/ 时正确——消费者工程下 workspace 会指向错误根,故优先用注入。
|
|
143
|
+
const importMeta = opts.importMeta || null;
|
|
144
|
+
// 调用方可注入 cardListCache 实现(server/core 上下文,跨三态正确定位并刷新进程内单例)。
|
|
145
|
+
const cardListCacheMod = opts.cardListCache || null;
|
|
146
|
+
if (cardListCacheMod) _injectedCardListCache = cardListCacheMod;
|
|
133
147
|
const startPage = Number(opts.startPage || 1);
|
|
134
148
|
const startIndex = Number(opts.startIndex || 0);
|
|
135
149
|
const pageSize = Number(opts.pageSize || 24);
|
|
@@ -403,6 +417,107 @@ export async function previewCardRemote(schema, { skeletonScreen = '', publish =
|
|
|
403
417
|
}
|
|
404
418
|
}
|
|
405
419
|
|
|
420
|
+
/**
|
|
421
|
+
* 远端发布:取当前卡片远端最大版本号 +1,发布上线为新版本。
|
|
422
|
+
* 链路(对齐 ubomp 发布流程,仅两步):
|
|
423
|
+
* 1) IDC10004.do 取当前卡片最大版本号 maxVersion;
|
|
424
|
+
* 2) IDC10016.do 以 maxVersion+1 作为新版本号发布(发布的是远端「当前已保存」的卡片内容——
|
|
425
|
+
* 故发布前应先用「推送当前卡」把本地内容保存到远端,否则会发布陈旧内容)。
|
|
426
|
+
* 会话由 ensureFinMallSession 自动保证;与 saveCardToRemote / syncCards / previewCardRemote
|
|
427
|
+
* 共用同一套已登录后台 Chrome,命中会话/权限失效(BLEC0006 等)时自动清 session 重登重试一次。
|
|
428
|
+
*
|
|
429
|
+
* @param {object} schema 当前卡片 schema(需含 pageId)
|
|
430
|
+
* @returns {Promise<{maxVersion:string, nextVersion:string, response:object}>}
|
|
431
|
+
*/
|
|
432
|
+
export async function publishCard(schema) {
|
|
433
|
+
if (!schema || !schema.pageId) {
|
|
434
|
+
throw new Error('schema 缺少 pageId,无法确定要发布的卡片(IDC10016.do 需要 cardId)。')
|
|
435
|
+
}
|
|
436
|
+
const appId = finmallConfig.appId
|
|
437
|
+
const workspaceId = finmallConfig.workspaceId
|
|
438
|
+
const cardId = String(schema.pageId)
|
|
439
|
+
const session = await ensureFinMallSession()
|
|
440
|
+
const browser = session.browser
|
|
441
|
+
try {
|
|
442
|
+
const context = browser.contexts()[0]
|
|
443
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0]
|
|
444
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。')
|
|
445
|
+
await page.bringToFront()
|
|
446
|
+
|
|
447
|
+
// 1) 取当前最大版本号
|
|
448
|
+
const maxResp = await finmallPostWithHeal(
|
|
449
|
+
page,
|
|
450
|
+
`/ubomp-web/IDC10004.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
451
|
+
{ cardId, pageNo: 1, pageSize: 1, sassAppId: appId, sassWorkspaceId: workspaceId },
|
|
452
|
+
'IDC10004.do',
|
|
453
|
+
)
|
|
454
|
+
const maxVersion = String(maxResp && maxResp.maxVersion != null ? maxResp.maxVersion : '0')
|
|
455
|
+
const maxNum = Number(maxVersion)
|
|
456
|
+
const nextVersion = String(Number.isFinite(maxNum) ? maxNum + 1 : 1)
|
|
457
|
+
log.info('发布前取最大版本', { cardId, maxVersion, nextVersion })
|
|
458
|
+
|
|
459
|
+
// 2) 以 maxVersion+1 发布(发布类型/平台等参数对齐 ubomp 发布弹窗的默认值)
|
|
460
|
+
const response = await finmallPostWithHeal(
|
|
461
|
+
page,
|
|
462
|
+
`/ubomp-web/IDC10016.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
463
|
+
{
|
|
464
|
+
cardId,
|
|
465
|
+
version: nextVersion,
|
|
466
|
+
platformStatus: JSON.stringify(['iOS', 'Android', 'HarmonyOS']),
|
|
467
|
+
publishType: '2',
|
|
468
|
+
publishMode: '1',
|
|
469
|
+
whitelistIds: '',
|
|
470
|
+
realGreyEndtime: '',
|
|
471
|
+
greyNum: '1',
|
|
472
|
+
memo: '',
|
|
473
|
+
greyConfigInfo: '[]',
|
|
474
|
+
sassAppId: appId,
|
|
475
|
+
sassWorkspaceId: workspaceId,
|
|
476
|
+
},
|
|
477
|
+
'IDC10016.do',
|
|
478
|
+
)
|
|
479
|
+
log.ok('卡片发布完成', { cardId, nextVersion })
|
|
480
|
+
return { maxVersion, nextVersion, response }
|
|
481
|
+
} finally {
|
|
482
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* 取远端「当前卡片最新版本号」:IDC10004 返回 maxVersion(该卡线上已发布的最高版本)。
|
|
488
|
+
* 供 UI「卡片来源」tab 展示「远端最新版本」,让用户一眼看到线上当前版本。
|
|
489
|
+
* 只读、不发布;与 publishCard 共用同一后台 FinMall 会话(ensureFinMallSession + finmallPostWithHeal)。
|
|
490
|
+
* @param {string|number} cardId
|
|
491
|
+
* @returns {Promise<{ maxVersion: string }>}
|
|
492
|
+
*/
|
|
493
|
+
export async function fetchCardLatestVersion(cardId) {
|
|
494
|
+
if (cardId == null) {
|
|
495
|
+
throw new Error('缺少 cardId,无法查询远端版本(IDC10004.do 需要 cardId)。')
|
|
496
|
+
}
|
|
497
|
+
const appId = finmallConfig.appId
|
|
498
|
+
const workspaceId = finmallConfig.workspaceId
|
|
499
|
+
const id = String(cardId)
|
|
500
|
+
const session = await ensureFinMallSession()
|
|
501
|
+
const browser = session.browser
|
|
502
|
+
try {
|
|
503
|
+
const context = browser.contexts()[0]
|
|
504
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0]
|
|
505
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。')
|
|
506
|
+
await page.bringToFront()
|
|
507
|
+
const maxResp = await finmallPostWithHeal(
|
|
508
|
+
page,
|
|
509
|
+
`/ubomp-web/IDC10004.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
510
|
+
{ cardId: id, pageNo: 1, pageSize: 1, sassAppId: appId, sassWorkspaceId: workspaceId },
|
|
511
|
+
'IDC10004.do',
|
|
512
|
+
)
|
|
513
|
+
const maxVersion = String(maxResp && maxResp.maxVersion != null ? maxResp.maxVersion : '0')
|
|
514
|
+
log.info('查询远端最新版本', { cardId: id, maxVersion })
|
|
515
|
+
return { maxVersion }
|
|
516
|
+
} finally {
|
|
517
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
406
521
|
// —— 保存落库校验辅助 ——
|
|
407
522
|
|
|
408
523
|
/** 稳定序列化:对象键排序、带 id/key/className/name 的数组元素按该键排序,避免服务端字段重排导致指纹误判。 */
|
|
@@ -670,6 +785,7 @@ async function syncCard(page, card, { outRoot, appId, workspaceId, summary, sche
|
|
|
670
785
|
appId,
|
|
671
786
|
workspaceId,
|
|
672
787
|
card,
|
|
788
|
+
importMeta,
|
|
673
789
|
files: { schema: schemaInfo.size, data: data.size, cover: cover.size },
|
|
674
790
|
});
|
|
675
791
|
return { cover: cover.size, schema: schemaInfo.size, data: data.size };
|
|
@@ -725,6 +841,7 @@ async function syncBlankCard(page, card, { cardDir, appId, workspaceId }) {
|
|
|
725
841
|
appId,
|
|
726
842
|
workspaceId,
|
|
727
843
|
card,
|
|
844
|
+
importMeta,
|
|
728
845
|
files: { schema: schemaInfo.size, data: dataInfo.size, cover: coverInfo ? coverInfo.size : 0 },
|
|
729
846
|
});
|
|
730
847
|
|
|
@@ -835,11 +952,10 @@ async function findExistingCover(cardDir, id) {
|
|
|
835
952
|
* 侧车独立于 schema.json/data.json,绝不污染 DSL 产品数据;找不到 server 模块时静默跳过。
|
|
836
953
|
* 空白卡同样写侧车——pageId 已对齐远端 cardId,属于合法的远端来源卡,允许后续推送。
|
|
837
954
|
*/
|
|
838
|
-
async function writeImportSidecar(cardDir, { appId, workspaceId, card, files }) {
|
|
955
|
+
async function writeImportSidecar(cardDir, { appId, workspaceId, card, files, importMeta }) {
|
|
839
956
|
try {
|
|
840
|
-
const
|
|
841
|
-
const
|
|
842
|
-
const meta = buildImportMeta({
|
|
957
|
+
const mod = importMeta || (await import(pathToFileURL(path.resolve(workspace, 'server', 'core', 'cardImportMeta.js')).href));
|
|
958
|
+
const meta = mod.buildImportMeta({
|
|
843
959
|
baseUrl: finmallConfig.baseUrl,
|
|
844
960
|
appId,
|
|
845
961
|
workspaceId,
|
|
@@ -848,7 +964,7 @@ async function writeImportSidecar(cardDir, { appId, workspaceId, card, files })
|
|
|
848
964
|
cardName: card.title,
|
|
849
965
|
files,
|
|
850
966
|
});
|
|
851
|
-
writeImportMeta(cardDir, meta);
|
|
967
|
+
mod.writeImportMeta(cardDir, meta);
|
|
852
968
|
} catch (e) {
|
|
853
969
|
log.warn('写导入元数据侧车失败(不影响卡片落盘)', { error: e && e.message });
|
|
854
970
|
}
|
|
@@ -35,8 +35,9 @@ import { finmallConfig } from '../../shared/finmall-config.mjs';
|
|
|
35
35
|
|
|
36
36
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
37
37
|
const skillDir = path.resolve(scriptDir, '..');
|
|
38
|
-
//
|
|
39
|
-
|
|
38
|
+
// 项目根:非注入形态(纯 CLI)用 cwd,跨「仓库内 / 消费者工程」一致;server 中间件始终注入绝对 root。
|
|
39
|
+
// 以往按 skills 位置向上跳两级的写法在「npx cardbuddy init」消费者工程下会指向错误根。
|
|
40
|
+
const workspace = process.cwd();
|
|
40
41
|
|
|
41
42
|
// 统一日志美化(容错:缺 server/core/logger 时回退极简实现,保证脚本可独立运行)。
|
|
42
43
|
let log;
|
|
@@ -433,8 +434,9 @@ function rewriteCardRefs(cardFiles, root, mapping) {
|
|
|
433
434
|
* @returns {Promise<object>} 汇总结果
|
|
434
435
|
*/
|
|
435
436
|
export async function uploadSlices(opts = {}) {
|
|
436
|
-
|
|
437
|
-
const
|
|
437
|
+
// 非注入形态(纯 CLI)用 cwd 作为项目根,跨「仓库内 / 消费者工程」一致;server 中间件始终注入绝对 slicesRoot/cardsRoot。
|
|
438
|
+
const slicesRoot = path.resolve(opts.slicesRoot || path.join(process.cwd(), 'src', 'slices'));
|
|
439
|
+
const cardsRoot = path.resolve(opts.cardsRoot || path.join(process.cwd(), 'src', 'cards'));
|
|
438
440
|
const manifestPath = path.join(slicesRoot, MANIFEST_FILE);
|
|
439
441
|
const appId = String(opts.appId || finmallConfig.appId);
|
|
440
442
|
const workspaceId = String(opts.workspaceId || finmallConfig.workspaceId);
|
|
@@ -677,8 +679,8 @@ if (isMain) {
|
|
|
677
679
|
noRewrite: !!args['no-rewrite'],
|
|
678
680
|
appId: args.appId ? String(args.appId) : undefined,
|
|
679
681
|
workspaceId: args.workspaceId ? String(args.workspaceId) : undefined,
|
|
680
|
-
slicesRoot: args.slicesRoot ? path.resolve(
|
|
681
|
-
cardsRoot: args.cardsRoot ? path.resolve(
|
|
682
|
+
slicesRoot: args.slicesRoot ? path.resolve(String(args.slicesRoot)) : undefined,
|
|
683
|
+
cardsRoot: args.cardsRoot ? path.resolve(String(args.cardsRoot)) : undefined,
|
|
682
684
|
});
|
|
683
685
|
const lines = [
|
|
684
686
|
`目标:sassAppId=${summary.appId} sassWorkspaceId=${summary.workspaceId}${summary.dryRun ? '(dry-run,未上传)' : ''}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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": {
|