diff --git a/README.en.md b/README.en.md index 8b59bc6a..4225b127 100644 --- a/README.en.md +++ b/README.en.md @@ -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) diff --git a/bds-tools/package.json b/bds-tools/package.json index d5716db8..6fc18c33 100644 --- a/bds-tools/package.json +++ b/bds-tools/package.json @@ -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" diff --git a/bds-tools/src/check-update.ts b/bds-tools/src/check-update.ts index c8067aa5..6b865828 100644 --- a/bds-tools/src/check-update.ts +++ b/bds-tools/src/check-update.ts @@ -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, @@ -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, diff --git a/package-lock.json b/package-lock.json index 56334aed..24d1c6ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3557,7 +3557,6 @@ "chalk": "^5.4.1", "cli-progress": "^3.12.0", "jsonc-parser": "^3.3.1", - "jszip": "^3.10.1", "ws": "^8.21.1" }, "bin": { diff --git a/sfmc/package.json b/sfmc/package.json index 1b3a178c..a4054539 100644 --- a/sfmc/package.json +++ b/sfmc/package.json @@ -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": { diff --git a/sfmc/src/wizard.ts b/sfmc/src/wizard.ts index ea216bb7..5e6cc872 100644 --- a/sfmc/src/wizard.ts +++ b/sfmc/src/wizard.ts @@ -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, @@ -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"; @@ -37,54 +37,6 @@ async function waitForHealth(port: number, ms = 15000): Promise { return false; } -async function extractZip( - data: Buffer, - destDir: string, - options: { overwrite?: boolean } = { overwrite: true } -): Promise { - 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" }, @@ -130,7 +82,8 @@ async function prepareRuntimeAssets(rootDir: string): Promise { 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"); }, @@ -139,7 +92,7 @@ async function prepareRuntimeAssets(rootDir: string): Promise { return; } - /* monorepo??? configs/ modules/?npm ?????????? */ + /* monorepo 布局:configs/ modules/ 应由 npm 工作区已提供 */ if (isMonorepoLayout(rootDir)) { const missing = missingRuntimeAssets(rootDir); if (missing.length > 0) { @@ -151,7 +104,7 @@ async function prepareRuntimeAssets(rootDir: string): Promise { seedNpmRuntimeLayout(rootDir); } -/** ? npm ??/?????? configs + modules ??????????? */ +/** 非 monorepo 的 npm 安装布局:播种 configs + 空 modules 骨架 */ function seedNpmRuntimeLayout(rootDir: string): void { const defaultsDir = resolveDefaultsDir(); const configsDest = path.join(rootDir, "configs");