From bcbb5e3bfadc1504b630bba968d3b280e98c8a67 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 11:08:50 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix(bds-tools):=20=E4=BF=AE=E5=A4=8D=20scan?= =?UTF-8?q?DestOccupancy=20=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=20--dts=20=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #80/#81 合入时 scanDestOccupancy 保留了 facts 外壳却残留未声明的 uuid/version/name 赋值,changeset-release/ootb 的 tsc7 --dts 挂掉; 运行时 ReferenceError 被 catch 吞掉会使占用 uuid 恒空。 改为 facts = readPackDirOccupancy(dir)(DRY),补 scan 单测; 清理已写入 pre.json 的残留 changeset 文件,避免与 pending 混淆。 Co-authored-by: Shiroha --- .changeset/align-release-token-dry.md | 5 ----- .changeset/bds-log-filter-solid.md | 6 ------ .changeset/build-deps-world-packs-solid.md | 2 +- bds-tools/src/world-packs.ts | 16 +++------------- bds-tools/world-packs.test.mjs | 20 ++++++++++++++++++++ 5 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 .changeset/align-release-token-dry.md delete mode 100644 .changeset/bds-log-filter-solid.md diff --git a/.changeset/align-release-token-dry.md b/.changeset/align-release-token-dry.md deleted file mode 100644 index 53d00a2..0000000 --- a/.changeset/align-release-token-dry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@sfmc-bds/tools": patch ---- - -发版编排:GitHub Release 缺失态回退复用 listPackagesWithExistingVersionTags;文档/workflow 对齐 SFMC_GITHUB_TOKEN(兼容旧名) diff --git a/.changeset/bds-log-filter-solid.md b/.changeset/bds-log-filter-solid.md deleted file mode 100644 index 5f58462..0000000 --- a/.changeset/bds-log-filter-solid.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@sfmc-bds/sdk": patch -"@sfmc-bds/cli": patch ---- - -fix(logs/config): BDS 级别解析 DRY 到 SDK;剥前缀后勿误判 Error;readJson 剥 BOM;log-filter 走 ensureSchemaConfig diff --git a/.changeset/build-deps-world-packs-solid.md b/.changeset/build-deps-world-packs-solid.md index a0a50ab..21ae0f8 100644 --- a/.changeset/build-deps-world-packs-solid.md +++ b/.changeset/build-deps-world-packs-solid.md @@ -3,4 +3,4 @@ "@sfmc-bds/bds-tools": patch --- -发版:build-publishable 拓扑 + listPublishableBuildDeps(npm-publish 应急补发不再硬编码只 build SDK);push 缺失态 DRY 对齐 listUnpushedExistingVersionTags。世界包:readPackDirOccupancy DRY,去掉死不变式,occupancy 保留真实 kind(LSP)。 +发版:build-publishable 拓扑 + listPublishableBuildDeps(npm-publish 应急补发不再硬编码只 build SDK);push 缺失态 DRY 对齐 listUnpushedExistingVersionTags。世界包:readPackDirOccupancy DRY,去掉死不变式,occupancy 保留真实 kind(LSP)。修 #80/#81 合并冲突:scanDestOccupancy 须赋值 facts=readPackDirOccupancy,禁止残留未声明 uuid/version/name 赋值(否则 --dts 挂掉且 catch 吞掉 ReferenceError 导致占用 uuid 恒空)。 diff --git a/bds-tools/src/world-packs.ts b/bds-tools/src/world-packs.ts index 1ac04ba..4da310a 100644 --- a/bds-tools/src/world-packs.ts +++ b/bds-tools/src/world-packs.ts @@ -566,7 +566,7 @@ export type PackInstallPlan = /** * 读单目录占用事实(info 优先,否则 header)。 - * Utf8BomError 上抛,由调用方决定跳过或仍占文件夹名。 + * 读失败上抛,由调用方决定跳过或仍占文件夹名(uuid 置空)。 */ export function readPackDirOccupancy(dir: string): { uuid: string; @@ -596,18 +596,8 @@ export function scanDestOccupancy(destParent: string): DestOccupancy[] { for (const dir of listPackDirsIn(destParent)) { let facts: ReturnType = null; try { - const info = readPackManifestInfo(dir); - if (info) { - uuid = info.uuid; - version = info.version; - name = info.name; - } else { - const header = readPackManifestHeader(dir); - if (header) { - uuid = header.uuid; - version = header.version; - } - } + // DRY:单目录占用只走 readPackDirOccupancy,禁止再内联一份 info/header 分支 + facts = readPackDirOccupancy(dir); } catch { /* manifest 不可读则占位 uuid 为空 */ } diff --git a/bds-tools/world-packs.test.mjs b/bds-tools/world-packs.test.mjs index 0a734f7..93a513a 100644 --- a/bds-tools/world-packs.test.mjs +++ b/bds-tools/world-packs.test.mjs @@ -283,6 +283,26 @@ describe("world-packs primitives", () => { assert.deepEqual(version, [2, 0, 1]); }); + it("scanDestOccupancy 走 readPackDirOccupancy(含 kind)", async () => { + const { scanDestOccupancy } = await import("./dist/world-packs.js"); + const parent = path.join(tmp, "occupancy-scan"); + const dir = path.join(parent, "[BP] Occ"); + const uuid = "cccccccc-cccc-cccc-cccc-cccccccccccc"; + writeManifest(dir, { + name: "OccPack", + uuid, + version: [2, 3, 4], + type: "data", + }); + const rows = scanDestOccupancy(parent); + assert.equal(rows.length, 1); + assert.equal(rows[0].folderName, "[BP] Occ"); + assert.equal(rows[0].uuid, uuid); + assert.deepEqual(rows[0].version, [2, 3, 4]); + assert.equal(rows[0].name, "OccPack"); + assert.equal(rows[0].kind, "behavior"); + }); + it("decidePackInstallPlan 表驱动", async () => { const { decidePackInstallPlan, formatWorldPackFolderName } = await import("./dist/world-packs.js"); const uuidA = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"; From fca293740d8d440ebd7988533cfc01540b3540da Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 11:10:05 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix(bds-tools):=20=E5=BC=BA=E5=8C=96=20occu?= =?UTF-8?q?pancy=20=E5=8D=95=E6=B5=8B=E5=B9=B6=E5=90=8C=E6=AD=A5=20lock?= =?UTF-8?q?=EF=BC=88=E8=B6=85=E9=9B=86=E5=B9=B6=E8=A1=8C=20#85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留 #81 扫描不中断契约(try/catch),采用完整+残缺 header 覆盖; 同步 #84 后 package-lock 中 sdk/cli 的 0.2.0-beta.1。 Co-authored-by: Shiroha --- bds-tools/src/world-packs.ts | 7 ++-- bds-tools/world-packs.test.mjs | 59 ++++++++++++++++++++++++---------- package-lock.json | 8 ++--- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/bds-tools/src/world-packs.ts b/bds-tools/src/world-packs.ts index 4da310a..7e0411f 100644 --- a/bds-tools/src/world-packs.ts +++ b/bds-tools/src/world-packs.ts @@ -566,7 +566,8 @@ export type PackInstallPlan = /** * 读单目录占用事实(info 优先,否则 header)。 - * 读失败上抛,由调用方决定跳过或仍占文件夹名(uuid 置空)。 + * info/header 解析失败返回 null;BOM 已由 readJsonFile 剥离。 + * 其它异常上抛,由 scanDestOccupancy 决定仍占文件夹名(uuid 置空)。 */ export function readPackDirOccupancy(dir: string): { uuid: string; @@ -590,7 +591,7 @@ export function readPackDirOccupancy(dir: string): { return null; } -/** 扫描 destParent 下含 manifest 的目录占用 */ +/** 扫描 destParent 下含 manifest 的目录占用(占用事实唯一入口:readPackDirOccupancy) */ export function scanDestOccupancy(destParent: string): DestOccupancy[] { const out: DestOccupancy[] = []; for (const dir of listPackDirsIn(destParent)) { @@ -599,7 +600,7 @@ export function scanDestOccupancy(destParent: string): DestOccupancy[] { // DRY:单目录占用只走 readPackDirOccupancy,禁止再内联一份 info/header 分支 facts = readPackDirOccupancy(dir); } catch { - /* manifest 不可读则占位 uuid 为空 */ + /* manifest 不可读则占位 uuid 为空(#81 契约:扫描本身不因单目录失败而中断) */ } out.push({ folderName: path.basename(dir), diff --git a/bds-tools/world-packs.test.mjs b/bds-tools/world-packs.test.mjs index 93a513a..25dc532 100644 --- a/bds-tools/world-packs.test.mjs +++ b/bds-tools/world-packs.test.mjs @@ -283,24 +283,49 @@ describe("world-packs primitives", () => { assert.deepEqual(version, [2, 0, 1]); }); - it("scanDestOccupancy 走 readPackDirOccupancy(含 kind)", async () => { - const { scanDestOccupancy } = await import("./dist/world-packs.js"); - const parent = path.join(tmp, "occupancy-scan"); - const dir = path.join(parent, "[BP] Occ"); - const uuid = "cccccccc-cccc-cccc-cccc-cccccccccccc"; - writeManifest(dir, { - name: "OccPack", - uuid, - version: [2, 3, 4], - type: "data", + it("scanDestOccupancy 走 readPackDirOccupancy(完整 + 残缺 header)", async () => { + const { scanDestOccupancy, readPackDirOccupancy } = await import("./dist/world-packs.js"); + const dest = path.join(tmp, "occupancy-dry"); + const fullDir = path.join(dest, "[RP] Full"); + writeManifest(fullDir, { + name: "Full Pack", + uuid: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + version: [2, 1, 0], + type: "resources", }); - const rows = scanDestOccupancy(parent); - assert.equal(rows.length, 1); - assert.equal(rows[0].folderName, "[BP] Occ"); - assert.equal(rows[0].uuid, uuid); - assert.deepEqual(rows[0].version, [2, 3, 4]); - assert.equal(rows[0].name, "OccPack"); - assert.equal(rows[0].kind, "behavior"); + const partialDir = path.join(dest, "[RP] Partial"); + fs.mkdirSync(partialDir, { recursive: true }); + fs.writeFileSync( + path.join(partialDir, "manifest.json"), + JSON.stringify({ + format_version: 2, + header: { + name: "Partial", + uuid: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + version: [3, 0, 0], + }, + modules: [], + }) + ); + + const fullFacts = readPackDirOccupancy(fullDir); + assert.equal(fullFacts?.uuid, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + assert.deepEqual(fullFacts?.version, [2, 1, 0]); + assert.equal(fullFacts?.name, "Full Pack"); + assert.equal(fullFacts?.kind, "resource"); + + const partialFacts = readPackDirOccupancy(partialDir); + assert.equal(partialFacts?.uuid, "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + assert.deepEqual(partialFacts?.version, [3, 0, 0]); + assert.equal(partialFacts?.kind, undefined); + + const occ = scanDestOccupancy(dest); + assert.equal(occ.length, 2); + const byUuid = new Map(occ.map((o) => [o.uuid, o])); + assert.equal(byUuid.get("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")?.name, "Full Pack"); + assert.equal(byUuid.get("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")?.kind, "resource"); + assert.equal(byUuid.get("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")?.version?.[0], 3); + assert.equal(byUuid.get("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")?.name, undefined); }); it("decidePackInstallPlan 表驱动", async () => { diff --git a/package-lock.json b/package-lock.json index 11ba367..935e961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ }, "modules/sdk/@sfmc-sdk": { "name": "@sfmc-bds/sdk", - "version": "0.2.0-beta.0", + "version": "0.2.0-beta.1", "license": "ISC", "engines": { "node": ">=22.13.0" @@ -4623,18 +4623,18 @@ "name": "@sfmc-bds/remote-controller", "version": "0.1.0", "dependencies": { - "@sfmc-bds/sdk": "^0.2.0-beta.0", + "@sfmc-bds/sdk": "^0.2.0-beta.1", "ws": "^8.21.1" } }, "sfmc": { "name": "@sfmc-bds/cli", - "version": "0.2.0-beta.0", + "version": "0.2.0-beta.1", "license": "ISC", "dependencies": { "@clack/prompts": "^1.7.0", "@sfmc-bds/bds-tools": "^0.2.0-beta.0", - "@sfmc-bds/sdk": "^0.2.0-beta.0", + "@sfmc-bds/sdk": "^0.2.0-beta.1", "chalk": "^5.4.1", "ws": "^8.21.1" }, From c45b202c9e0c431382fc0bb993aac27099308ff6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 11:10:59 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix(release):=20=E6=81=A2=E5=A4=8D=20pre=20?= =?UTF-8?q?mode=20=E5=BA=94=E4=BF=9D=E7=95=99=E7=9A=84=E5=B7=B2=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=20changeset=20=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pre 模式下 apply-release-plan 不会删除 .md(仅 exit/非 pre 才删), 须留盘至 pre exit;listPendingChangesetFiles 靠 pre.json 排除。 误删会破坏 pre exit 时对历史 changeset 的再计算。 Co-authored-by: Shiroha --- .changeset/align-release-token-dry.md | 5 +++++ .changeset/bds-log-filter-solid.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/align-release-token-dry.md create mode 100644 .changeset/bds-log-filter-solid.md diff --git a/.changeset/align-release-token-dry.md b/.changeset/align-release-token-dry.md new file mode 100644 index 0000000..53d00a2 --- /dev/null +++ b/.changeset/align-release-token-dry.md @@ -0,0 +1,5 @@ +--- +"@sfmc-bds/tools": patch +--- + +发版编排:GitHub Release 缺失态回退复用 listPackagesWithExistingVersionTags;文档/workflow 对齐 SFMC_GITHUB_TOKEN(兼容旧名) diff --git a/.changeset/bds-log-filter-solid.md b/.changeset/bds-log-filter-solid.md new file mode 100644 index 0000000..5f58462 --- /dev/null +++ b/.changeset/bds-log-filter-solid.md @@ -0,0 +1,6 @@ +--- +"@sfmc-bds/sdk": patch +"@sfmc-bds/cli": patch +--- + +fix(logs/config): BDS 级别解析 DRY 到 SDK;剥前缀后勿误判 Error;readJson 剥 BOM;log-filter 走 ensureSchemaConfig