Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ jobs:

- uses: actions/setup-node@v5
with:
# 故意独立于 .node-version:SEA bundle 目标 node26(见 build-sea.mjs target)
node-version: "26"
# 故意独立于 .node-version(烟测 CI);SEA 目标唯一来源:.node-version-sea
# (与 build-sea.mjs target 共用,DRY)
node-version-file: .node-version-sea
cache: npm

- run: npm ci --no-audit --no-fund
Expand Down
1 change: 1 addition & 0 deletions .node-version-sea
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
11 changes: 11 additions & 0 deletions bds-tools/pack-manager-lifecycle.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ describe("pack-manager CLI extensions", () => {
assert.deepEqual(readWorldPackList(worldsDir, "L1", "behavior"), listed);
});

it("readLevelNameSync 与 async 同契约(供 sfmc resolveBdsContext)", async () => {
const { readLevelName, readLevelNameSync } = await import("./dist/pack-manager.js");
const bds = path.join(tmp, "bds-level");
fs.mkdirSync(bds, { recursive: true });
assert.equal(readLevelNameSync(bds), "Bedrock level");
assert.equal(await readLevelName(bds), "Bedrock level");
fs.writeFileSync(path.join(bds, "server.properties"), "level-name=My World\n", "utf8");
assert.equal(readLevelNameSync(bds), "My World");
assert.equal(await readLevelName(bds), "My World");
});

it("readWorldPackListResult 区分缺失与 JSON 损坏(doctor parseFail)", async () => {
const { readWorldPackListResult } = await import("./dist/pack-manager.js");
const worldsDir = path.join(tmp, "worlds-parse");
Expand Down
1 change: 1 addition & 0 deletions bds-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"./check-update": "./dist/check-update.js",
"./bds-manager": "./dist/bds-manager.js",
"./pack-manager": "./dist/cli-pack-manager.js",
"./pack-manager-lib": "./dist/pack-manager.js",
"./world-packs": "./dist/world-packs.js",
"./taskbar": "./dist/taskbar.js",
"./update-result": "./dist/update-result.js",
Expand Down
29 changes: 19 additions & 10 deletions bds-tools/src/pack-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
*
* 这是 SEA 与 npm 两条用户路径共用的纯函数库:
*
* - SEA:用户在 REPL/wizard 里跑 `sfmc behavior-pack build/deploy`
* → sfmc 通过 spawnService 启 `bds-tools/dist/pack-manager.js`,
* SEA 自己跑 esbuild 把脚本 bundle 写到 <modulesDir>/build/sfmc-modules-bp/scripts/main.js,
* 然后调 pack-manager#assembleBehaviorPack 装配。
* - npm: 用户跑 `sfmc behavior-pack build`,
* 同一 spawnService 路径。或者直接在同进程 import 这个库。
* - 同进程(推荐): sfmc/pack-lifecycle 经 `@sfmc-bds/bds-tools/pack-manager-lib`
* 直连本模块(DIP);world-packs 等同理。
* - CLI: `cli-pack-manager.js` 仍暴露相同动词,供外部脚本/手工调试;
* CLI 只是本库的薄适配,不再是 sfmc 主路径。
*
* 关键约束:本文件不允许引 esbuild / 任何 npm-only 包 — SEA 要把它打成 SEA
* 的一部分内嵌起来,而 SEA 是个单 exe (除 Node 内置 + bds-tools 已有依赖外)。
Expand Down Expand Up @@ -185,17 +183,28 @@ export async function assembleResourcePack(opts: AssembleResourcePackOpts): Prom
* Falls back to "Bedrock level" (BDS default) when the file is missing or
* the key is absent.
*/
export async function readLevelName(bdsRoot: string): Promise<string> {
const file = path.join(bdsRoot, "server.properties");
if (!fs.existsSync(file)) return "Bedrock level";
const text = await fs.promises.readFile(file, "utf8");
function parseLevelNameFromProperties(text: string): string {
for (const line of text.split(/\r?\n/)) {
const m = /^\s*level-name\s*=\s*(.+?)\s*$/.exec(line);
if (m && m[1]) return m[1];
}
return "Bedrock level";
}

/** 同步读 level-name(供 sfmc resolveBdsContext 等同进程调用方,DRY)。 */
export function readLevelNameSync(bdsRoot: string): string {
const file = path.join(bdsRoot, "server.properties");
if (!fs.existsSync(file)) return "Bedrock level";
return parseLevelNameFromProperties(fs.readFileSync(file, "utf8"));
}

export async function readLevelName(bdsRoot: string): Promise<string> {
const file = path.join(bdsRoot, "server.properties");
if (!fs.existsSync(file)) return "Bedrock level";
const text = await fs.promises.readFile(file, "utf8");
return parseLevelNameFromProperties(text);
}

/**
* Copy BP/RP source dirs into the world's behavior_packs/ + resource_packs/
* folders, and write a fresh `permissions.json` into the BP target.
Expand Down
14 changes: 12 additions & 2 deletions build-sea.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import * as esbuild from "esbuild";
import fs from "node:fs";
import { mkdir, rm } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

await rm("dist/sea", { recursive: true, force: true });
await mkdir("dist/sea", { recursive: true });

/* SEA Node 目标唯一来源:仓库根 .node-version-sea(与 release.yml 共用,DRY) */
const root = path.dirname(fileURLToPath(import.meta.url));
const seaNodeMajor = fs.readFileSync(path.join(root, ".node-version-sea"), "utf8").trim();
if (!/^\d+$/.test(seaNodeMajor)) {
throw new Error(`.node-version-sea 应为纯主版本号,当前: ${JSON.stringify(seaNodeMajor)}`);
}
const seaTarget = `node${seaNodeMajor}`;

const result = await esbuild.build({
entryPoints: ["sfmc/src/dispatcher.ts"],
metafile: true,
bundle: true,
platform: "node",
target: "node26",
target: seaTarget,
format: "esm",
outfile: "dist/sea/dispatcher.mjs",
external: ["node:readline", "node:fs", "node:path"],
Expand All @@ -26,4 +36,4 @@ const result = await esbuild.build({
},
});
fs.writeFileSync("meta.json", JSON.stringify(result.metafile, null, 2));
console.log("SEA dispatcher bundle -> dist/sea/dispatcher.mjs");
console.log(`SEA dispatcher bundle -> dist/sea/dispatcher.mjs (target ${seaTarget})`);
Loading