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
12 changes: 11 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,18 @@ CheckNetIsolation LoopbackExempt -is -n=Microsoft.MinecraftUWP_8wekyb3d8bbwe

## License

[MIT](./LICENSE)
[AGPL-3.0](./LICENSE)

* **Freedom**: You may run, copy, distribute, and modify the program, provided those freedoms are preserved.
* **Copyleft**: If you distribute a modified version, you must provide the complete Corresponding Source under the same license (AGPL v3).
* **Source**: Corresponding Source must include all scripts, interface definitions, shared libraries, etc. needed for others to rebuild and modify.
* **Additional terms**: You may add further permissions, but not further restrictions (section 7).

---
> ⚠️ AI Assistance Disclaimer
> Portions of this project were produced with assistance from artificial intelligence (AI) tools for research, drafting, formatting, optimization, and development workflows.
>
> All AI-assisted content is human-reviewed, edited, and verified before publication.
> AI is used to improve productivity, accessibility, and workflow efficiency — not to replace human oversight, expertise, or judgment.

[中文版本 →](./README.md)
1 change: 1 addition & 0 deletions bds-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"./world-packs": "./dist/world-packs.js",
"./taskbar": "./dist/taskbar.js",
"./update-result": "./dist/update-result.js",
"./zipx": "./dist/zipx.js",
"./package.json": "./package.json",
"./types": {
"types": "./dist/types.d.ts"
Expand Down
3 changes: 1 addition & 2 deletions bds-tools/src/check-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import { httpDownload } from "./http.js";
import { isMainModule } from "./is-main.js";
import { loadConfig, LOG_PATH, resolvePaths } from "./paths.js";
import { sendText, sendWithImage } from "./qqutil.js";
import { emitUpdateResult } from "./update-result.js";
import { extractZipFileToDir } from "./zipx.js";
import {
clearRollbackMarker,
getDirSize,
Expand All @@ -32,6 +30,7 @@ import {
} from "./rollback.js";
import { ensureEmitServerTelemetry } from "./server-properties.js";
import { clearTaskbarProgress, isTaskbarSupported, setTaskbarProgress } from "./taskbar.js";
import { emitUpdateResult } from "./update-result.js";
import {
buildDownloadUrls,
fetchVersionDetails,
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion sfmc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"chalk": "^5.4.1",
"cli-progress": "^3.12.0",
"jsonc-parser": "^3.3.1",
"jszip": "^3.10.1",
"ws": "^8.21.1"
},
"engines": {
Expand Down
57 changes: 5 additions & 52 deletions sfmc/src/wizard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { confirm, intro, isCancel, multiselect, note, outro, select, tasks, text } from "@clack/prompts";
import { extractZipBufferToDir } from "@sfmc-bds/bds-tools/zipx";
import {
configPath,
modulePath,
Expand All @@ -9,7 +10,6 @@ import {
type ConfigName,
type ModuleLock,
} from "@sfmc-bds/sdk/node/config";
import JSZip from "jszip";
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
Expand Down Expand Up @@ -37,54 +37,6 @@ async function waitForHealth(port: number, ms = 15000): Promise<boolean> {
return false;
}

async function extractZip(
data: Buffer,
destDir: string,
options: { overwrite?: boolean } = { overwrite: true }
): Promise<string[]> {
const zip = await JSZip.loadAsync(data);
const files: string[] = [];

const safeJoin = (base: string, relative: string): string => {
const fullPath = path.resolve(base, relative);
const relativePath = path.relative(base, fullPath);
if (relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
throw new Error(`Path traversal detected: ${relative}`);
}
return fullPath;
};

const concurrency = 10;
const entries = Object.keys(zip.files);
const chunks = [];
for (let i = 0; i < entries.length; i += concurrency) {
chunks.push(entries.slice(i, i + concurrency));
}

for (const chunk of chunks) {
await Promise.all(
chunk.map(async (relPath) => {
if (relPath.endsWith("/")) return;

const file = zip.file(relPath);
if (!file) return;

const targetPath = safeJoin(destDir, relPath);
if (!options.overwrite && fs.existsSync(targetPath)) {
return;
}

await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
const content = await file.async("nodebuffer");
await fs.promises.writeFile(targetPath, content);
files.push(relPath);
})
);
}

return files;
}

/** SEA asset: { zipBaseName, targetDirRelative, SEA_asset_name } */
const ASSETS = [
{ target: "configs", npmTarget: "configs", asset: "configs_default" },
Expand Down Expand Up @@ -130,7 +82,8 @@ async function prepareRuntimeAssets(rootDir: string): Promise<void> {

const destDir = path.join(rootDir, target);
if (!ensureDirectory(destDir)) throw new Error(t("wizard.cannotCreateAssetDir", { dir: destDir }));
await extractZip(Buffer.from(assetBuffer), destDir, { overwrite: true });
// 安全解压委托 bds-tools/zipx(与 world-packs / check-update 同一权威)
await extractZipBufferToDir(Buffer.from(assetBuffer), destDir);
}
return t("wizard.assetsExtracted");
},
Expand All @@ -139,7 +92,7 @@ async function prepareRuntimeAssets(rootDir: string): Promise<void> {
return;
}

/* monorepo??? configs/ modules/?npm ?????????? */
/* monorepo 布局:configs/ modules/ 应由 npm 工作区已提供 */
if (isMonorepoLayout(rootDir)) {
const missing = missingRuntimeAssets(rootDir);
if (missing.length > 0) {
Expand All @@ -151,7 +104,7 @@ async function prepareRuntimeAssets(rootDir: string): Promise<void> {
seedNpmRuntimeLayout(rootDir);
}

/** ? npm ??/?????? configs + modules ??????????? */
/** 非 monorepo 的 npm 安装布局:播种 configs + modules 骨架 */
function seedNpmRuntimeLayout(rootDir: string): void {
const defaultsDir = resolveDefaultsDir();
const configsDest = path.join(rootDir, "configs");
Expand Down
Loading