UNPKG

minigame-std

Version:

Cross-platform standard library for WeChat minigame and web browsers with unified APIs for crypto, fs, fetch, storage, and more.

1,646 lines 60 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let happy_opfs = require("happy-opfs"); happy_opfs = __toESM(happy_opfs, 1); let happy_rusty = require("happy-rusty"); let _happy_ts_fetch_t = require("@happy-ts/fetch-t"); let fflate_browser = require("fflate/browser"); let tiny_future = require("tiny-future"); let src_std_internal_mod_ts = require("./_internal.cjs"); let src_std_path_mod_ts = require("./path.cjs"); let src_std_utils_mod_ts = require("./utils.cjs"); //#region src/macros/env.ts /** * 如果在小游戏环境中返回 true,否则返回 false。 */ const IS_MINA = __MINIGAME_STD_MINA__; //#endregion //#region src/std/fs/mina_fs_shared.ts /** * @internal * 同步/异步的公共代码。 */ /** * 小游戏文件系统管理器实例。 * */ const fs = /*#__PURE__*/ (0, happy_rusty.Lazy)(() => wx.getFileSystemManager()); /** * 用户数据根目录,`wxfile://usr` 或 `http://usr`。 * */ const usrPath = /*#__PURE__*/ (0, happy_rusty.Lazy)(() => wx.env.USER_DATA_PATH); /** * 根路径,`wxfile://` 或 `http://`。 * */ const rootPath = /*#__PURE__*/ (0, happy_rusty.Lazy)(() => { return `${usrPath.force().split("://")[0]}://`; }); const EMPTY_BYTES = /*#__PURE__*/ new Uint8Array(0); /** * 获取小游戏文件系统管理器实例。 * @returns 文件系统管理器实例。 */ function getFs() { return fs.force(); } /** * 获取文件系统的根路径。 * @returns 文件系统的根路径。 */ function getUsrPath() { return usrPath.force(); } /** * 验证并标准化路径,返回绝对路径。 * * 支持两种输入格式: * 1. 完整路径:以 `wxfile://` 或 `http://` 开头(如 `wxfile://usr/test`) * 2. 相对路径:以 `/` 开头(如 `/test`),会自动拼接 `wx.env.USER_DATA_PATH` * * @param path - 待验证的路径。 * @returns 验证成功返回标准化后的绝对路径,失败返回错误信息。 */ function validateAbsolutePath(path) { const typeError = validatePathType(path); if (typeError) return typeError; let isFullPath = false; if (path.startsWith(rootPath.force())) { isFullPath = true; path = path.slice(rootPath.force().length); if (!path) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error("Path must not be root directory")); } const normalized = (0, src_std_path_mod_ts.normalize)(path); path = normalized.length > 1 && normalized[normalized.length - 1] === happy_opfs.ROOT_DIR ? normalized.slice(0, -1) : normalized; if (isFullPath) { if (path === happy_opfs.ROOT_DIR) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error("Path must not be root directory")); if (path[0] === happy_opfs.ROOT_DIR) path = path.slice(1); return (0, happy_rusty.Ok)(rootPath.force() + path); } if (path[0] !== happy_opfs.ROOT_DIR) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Path must be absolute (start with '/'): '${path}'`)); return (0, happy_rusty.Ok)(usrPath.force() + path); } /** * 验证可读路径(用于只读操作:readFile、stat、readDir)。 * * 支持三种输入格式: * 1. 完整路径:以 `wxfile://` 或 `http://` 开头(如 `wxfile://usr/test`) * 2. 用户数据相对路径:以 `/` 开头(如 `/test`),会自动拼接 `wx.env.USER_DATA_PATH` * 3. 代码包路径:不以 `./`、`../` 开头的相对路径(如 `images/logo.png`),直接返回原路径 * * @param path - 待验证的路径。 * @returns 验证成功返回标准化后的路径,失败返回错误信息。 */ function validateReadablePath(path) { const typeError = validatePathType(path); if (typeError) return typeError; if (path.startsWith(rootPath.force()) || path.startsWith(happy_opfs.ROOT_DIR)) return validateAbsolutePath(path); if (path.startsWith("./") || path.startsWith("../")) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Invalid path: '${path}'. Code package paths must not start with './' or '../'`)); return (0, happy_rusty.Ok)((0, src_std_path_mod_ts.normalize)(path)); } /** * 验证提供的 ExistsOptions 是否有效。 * `isDirectory` 和 `isFile` 不能同时为 `true`。 * * @param options - 要验证的 ExistsOptions。 * @returns 表示成功或错误的 VoidIOResult。 */ function validateExistsOptions(options) { const { isDirectory = false, isFile = false } = options ?? {}; return isDirectory && isFile ? (0, happy_rusty.Err)(/* @__PURE__ */ new Error("isDirectory and isFile cannot both be true")) : happy_rusty.RESULT_VOID; } /** * 判断错误是否为 `NotFoundError`。 * @param error - 要检查的错误。 * @returns 如果是 `NotFoundError` 返回 `true`,否则返回 `false`。 */ function isNotFoundError(error) { return error.name === happy_opfs.NOT_FOUND_ERROR; } /** * 将错误对象转换为 IOResult 类型。 * @typeParam T - Result 的 Ok 类型。 * @param error - IO 操作的错误对象, 可以是同步(Error)或者异步(WechatMinigame.FileError)的。 * @returns 转换后的 IOResult 对象。 */ function fileErrorToResult(error) { const err = (0, src_std_internal_mod_ts.miniGameFailureToError)(error); if (isNotFoundFileError(err)) err.name = happy_opfs.NOT_FOUND_ERROR; return (0, happy_rusty.Err)(err); } /** * 处理 `mkdir` 的错误。 */ function fileErrorToMkdirResult(error) { return isAlreadyExistsFileError(error) ? happy_rusty.RESULT_VOID : fileErrorToResult(error); } /** * 处理 `remove` 的错误。 */ function fileErrorToRemoveResult(error) { return isNotFoundFileError(error) ? happy_rusty.RESULT_VOID : fileErrorToResult(error); } function createNothingToZipError() { const error = /* @__PURE__ */ new Error("Nothing to zip"); error.name = happy_opfs.NOTHING_TO_ZIP_ERROR; return (0, happy_rusty.Err)(error); } function createFileNotExistsError(filePath) { return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Cannot append to non-existent file: ${filePath}`)); } function createDirIsFileError(dirPath) { return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Path already exists but is a file: ${dirPath}`)); } /** * 获取读取文件的编码。 * @returns 返回 `'utf8'` 或 `undefined`(读取二进制时不传 encoding)。 */ function getReadFileEncoding(options) { return options?.encoding === "utf8" ? "utf8" : void 0; } /** * 获取写入文件的参数。 */ function getWriteFileContents(contents) { const isBin = typeof contents !== "string"; let data; if (isBin) { const result = (0, happy_rusty.tryResult)(() => (0, src_std_internal_mod_ts.bufferSourceToAb)(contents)); if (result.isErr()) return result.asErr(); data = result.unwrap(); } else data = contents; return (0, happy_rusty.Ok)({ data, encoding: isBin ? void 0 : "utf8" }); } /** * 获取 `exists` 的结果。 * * 注意:Windows 和 Mac 平台 `stat` 代码包内不存在的文件时, 平台会误报成功并返回 `stats.size = -1`。 * 此处通过 `stats.size < 0` 判断并视为不存在, 以兼容该平台 bug。 * * TODO(platform-wx): 待微信 Windows/Mac 平台修复该 stat 误报 bug 后, 移除 `stats.size < 0` 判断。 * 跟踪点: `mina_fs_shared.ts` getExistsResult。 */ function getExistsResult(statResult, options) { return statResult.map((stats) => { const { isDirectory = false, isFile = false } = options ?? {}; return !(stats.size < 0 || isDirectory && stats.isFile() || isFile && stats.isDirectory()); }).orElse((err) => { return isNotFoundError(err) ? happy_rusty.RESULT_FALSE : statResult.asErr(); }); } /** * 判断文件/目录是否存在(不区分类型)。 * * 相比 `stat` + `getExistsResult`,使用底层 `access` 接口开销更小,不会构造 `Stats` 对象。 * 当 `ExistsOptions` 不需要检查 `isDirectory`/`isFile` 时使用此实现。 * * @param path - 文件或目录路径。 * @returns 存在返回 `true`,不存在返回 `false`,其他错误透传。 */ async function accessExists(path) { const pathRes = validateReadablePath(path); if (pathRes.isErr()) return pathRes.asErr(); path = pathRes.unwrap(); return accessResultToExists(await (0, src_std_utils_mod_ts.asyncResultify)(getFs().access)({ path })); } /** * `accessExists` 的同步版本。 * * @param path - 文件或目录路径。 * @returns 存在返回 `true`,不存在返回 `false`,其他错误透传。 */ function accessExistsSync(path) { const pathRes = validateReadablePath(path); if (pathRes.isErr()) return pathRes.asErr(); path = pathRes.unwrap(); return accessResultToExists((0, happy_rusty.tryResult)(() => getFs().accessSync(path))); } /** * 根据 `recursive` 不同标准化 `stat` 的结果(recursive=true 的时候开发者工具对于文件和空文件夹会返回单个 Stats)。 * - `recursive=false`: 返回单个 `Stats` 或 `FileStats[]` * - `recursive=true`: 始终返回 `FileStats[]`,即使是单个文件或空目录 * - 如果是单个 `Stats`,包装成数组,path 设为 '' 表示当前项目 */ function normalizeStats(statsOrFileStats, recursive) { if (Array.isArray(statsOrFileStats)) return statsOrFileStats.map(({ path, stats }) => ({ path: path.replace(/^\/+/, ""), stats })).sort((a, b) => a.path.localeCompare(b.path)); return recursive ? [{ path: "", stats: statsOrFileStats }] : statsOrFileStats; } /** * 标准化同步或异步的文件错误对象。 * @param error - IO 操作的错误对象, 可以是同步(Error)或者异步(WechatMinigame.FileError)的。 */ function normalizeFileError(error) { return error instanceof Error ? { errCode: error.errno ?? 0, errMsg: error.message } : error; } /** * 判断是否文件或者文件夹不存在。 * @param error - IO 操作的错误对象, 可以是同步(Error)或者异步(WechatMinigame.FileError)的。 */ function isNotFoundFileError(error) { const { errCode, errMsg } = normalizeFileError(error); return errCode === 1300002 || errMsg.includes("no such file or directory"); } /** * 判断是否文件或者文件夹已存在。 * @param error - IO 操作的错误对象, 可以是同步(Error)或者异步(WechatMinigame.FileError)的。 */ function isAlreadyExistsFileError(error) { const { errCode, errMsg } = normalizeFileError(error); return errCode === 1301005 || errMsg.includes("already exists"); } /** * 验证 path 是否为字符串类型。 * @param path - 待验证的路径。 * @returns 如果不是字符串返回错误,否则返回 undefined。 */ function validatePathType(path) { if (typeof path !== "string") return (0, happy_rusty.Err)(/* @__PURE__ */ new TypeError(`Path must be a string but received ${typeof path}`)); } /** * 将 `access`/`accessSync` 调用的结果转换为存在性结果。 * - 成功 → `true` * - NotFound → `false` * - 其他错误 → 透传 */ function accessResultToExists(accessRes) { return accessRes.map(() => true).orElse((err) => { const normalized = fileErrorToResult(err).unwrapErr(); return isNotFoundError(normalized) ? happy_rusty.RESULT_FALSE : (0, happy_rusty.Err)(normalized); }); } //#endregion //#region src/std/fs/mina_fs_async.ts /** * @internal * 小游戏平台的异步文件系统操作实现。 */ /** * 递归创建文件夹,相当于`mkdir -p`。 * @param dirPath - 需要创建的目录路径。 * @returns 创建操作的异步结果。 */ async function mkdir$1(dirPath) { const dirPathRes = validateAbsolutePath(dirPath); if (dirPathRes.isErr()) return dirPathRes.asErr(); dirPath = dirPathRes.unwrap(); if (dirPath === getUsrPath()) return happy_rusty.RESULT_VOID; const statRes = await stat$2(dirPath); if (statRes.isOk()) { if (statRes.unwrap().isFile()) return createDirIsFileError(dirPath); return happy_rusty.RESULT_VOID; } return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().mkdir)({ dirPath, recursive: true })).and(happy_rusty.RESULT_VOID).orElse(fileErrorToMkdirResult); } /** * 移动或重命名文件或目录。 * @param srcPath - 原路径。 * @param destPath - 新路径。 * @returns 移动操作的异步结果。 */ async function move$1(srcPath, destPath) { const srcPathRes = validateAbsolutePath(srcPath); if (srcPathRes.isErr()) return srcPathRes.asErr(); srcPath = srcPathRes.unwrap(); const destPathRes = validateAbsolutePath(destPath); if (destPathRes.isErr()) return destPathRes.asErr(); destPath = destPathRes.unwrap(); return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().rename)({ oldPath: srcPath, newPath: destPath })).and(happy_rusty.RESULT_VOID).orElse(fileErrorToResult); } /** * 读取目录下的所有文件和子目录。 * @param dirPath - 目录路径。 * @returns 包含目录内容的字符串数组的异步结果。 */ async function readDir$2(dirPath) { const dirPathRes = validateReadablePath(dirPath); if (dirPathRes.isErr()) return dirPathRes.asErr(); dirPath = dirPathRes.unwrap(); return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().readdir)({ dirPath })).map((x) => x.files).orElse(fileErrorToResult); } /** * 读取文件内容,可选地指定编码和返回类型。 * @template T - 返回内容的类型。 * @param filePath - 文件路径。 * @param options - 可选的读取选项。 * @returns 包含文件内容的异步结果。 */ async function readFile$1(filePath, options) { const filePathRes = validateReadablePath(filePath); if (filePathRes.isErr()) return filePathRes.asErr(); filePath = filePathRes.unwrap(); const encoding = getReadFileEncoding(options); return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().readFile)({ filePath, encoding })).map((x) => { const { data } = x; return typeof data === "string" ? data : new Uint8Array(data); }).orElse(fileErrorToResult); } /** * 删除指定路径的文件或目录。 * @param path - 需要删除的文件或目录的路径。 * @returns 删除操作的异步结果。 */ async function remove$1(path) { const statRes = await stat$2(path); if (statRes.isErr()) return isNotFoundError(statRes.unwrapErr()) ? happy_rusty.RESULT_VOID : statRes.asErr(); path = validateAbsolutePath(path).unwrap(); return (await (statRes.unwrap().isDirectory() ? (0, src_std_utils_mod_ts.asyncResultify)(getFs().rmdir)({ dirPath: path, recursive: true }) : (0, src_std_utils_mod_ts.asyncResultify)(getFs().unlink)({ filePath: path }))).and(happy_rusty.RESULT_VOID).orElse(fileErrorToRemoveResult); } async function stat$2(path, options) { const pathRes = validateReadablePath(path); if (pathRes.isErr()) return pathRes.asErr(); path = pathRes.unwrap(); const { recursive = false } = options ?? {}; return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().stat)({ path, recursive })).map((x) => normalizeStats(x.stats, recursive)).orElse(fileErrorToResult); } /** * 将内容写入文件。 * @param filePath - 文件路径。 * @param contents - 要写入的内容。 * @param options - 可选的写入选项。 * @returns 写入操作的异步结果。 */ async function writeFile$1(filePath, contents, options) { const { append = false, create = true } = options ?? {}; const fs = getFs(); let writeMethod = fs.writeFile; if (append) { const existsRes = await exists$1(filePath); if (existsRes.isErr()) return existsRes.asErr(); if (existsRes.unwrap()) writeMethod = fs.appendFile; else { if (!create) return createFileNotExistsError(filePath); writeMethod = fs.writeFile; } } const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return filePathRes.asErr(); filePath = filePathRes.unwrap(); if (create && writeMethod === fs.writeFile) { const mkdirRes = await mkdir$1((0, src_std_path_mod_ts.dirname)(filePath)); if (mkdirRes.isErr()) return mkdirRes; } const contentsRes = getWriteFileContents(contents); if (contentsRes.isErr()) return contentsRes.asErr(); const { data, encoding } = contentsRes.unwrap(); return (await (0, src_std_utils_mod_ts.asyncResultify)(writeMethod)({ filePath, data, encoding })).and(happy_rusty.RESULT_VOID).orElse(fileErrorToResult); } /** * 向文件追加内容。 * @param filePath - 文件路径。 * @param contents - 要追加的内容。 * @param options - 可选的追加选项。 * @returns 追加操作的异步结果。 */ function appendFile$1(filePath, contents, options) { return writeFile$1(filePath, contents, { append: true, create: options?.create ?? true }); } /** * 复制文件或文件夹。 * * @param srcPath - 源文件或文件夹路径。 * @param destPath - 目标文件或文件夹路径。 * @returns 操作的异步结果。 */ async function copy$1(srcPath, destPath) { const destPathRes = validateAbsolutePath(destPath); if (destPathRes.isErr()) return destPathRes.asErr(); destPath = destPathRes.unwrap(); const statRes = await stat$2(srcPath, { recursive: true }); if (statRes.isErr()) return statRes.asErr(); srcPath = validateAbsolutePath(srcPath).unwrap(); for (const { path, stats } of statRes.unwrap()) { let copyRes; if (!path) if (stats.isDirectory()) copyRes = await mkdir$1(destPath); else copyRes = await (await mkdir$1((0, src_std_path_mod_ts.dirname)(destPath))).andThenAsync(() => { return copyFile(srcPath, destPath); }); else { const srcEntryPath = srcPath + src_std_path_mod_ts.SEPARATOR + path; const destEntryPath = destPath + src_std_path_mod_ts.SEPARATOR + path; copyRes = await (stats.isDirectory() ? mkdir$1(destEntryPath) : copyFile(srcEntryPath, destEntryPath)); } if (copyRes.isErr()) return copyRes; } return happy_rusty.RESULT_VOID; } /** * 检查指定路径的文件或目录是否存在。 * @param path - 文件或目录的路径。 * @param options - 可选的检查选项。 * @returns 检查存在性的异步结果,存在时返回 true。 */ async function exists$1(path, options) { const optionsRes = validateExistsOptions(options); if (optionsRes.isErr()) return optionsRes.asErr(); const { isDirectory = false, isFile = false } = options ?? {}; if (!isDirectory && !isFile) return accessExists(path); return getExistsResult(await stat$2(path), options); } /** * 清空目录中的所有文件和子目录。 * @param dirPath - 目录路径。 * @returns 清空操作的异步结果。 */ async function emptyDir$1(dirPath) { const readDirRes = await readDir$2(dirPath); if (readDirRes.isErr()) return isNotFoundError(readDirRes.unwrapErr()) ? mkdir$1(dirPath) : readDirRes.asErr(); dirPath = validateAbsolutePath(dirPath).unwrap(); const tasks = readDirRes.unwrap().map((name) => remove$1(dirPath + src_std_path_mod_ts.SEPARATOR + name)); return (await Promise.all(tasks)).find((x) => x.isErr()) ?? happy_rusty.RESULT_VOID; } /** * 读取文本文件的内容。 * @param filePath - 文件路径。 * @returns 包含文件文本内容的异步结果。 */ function readTextFile$1(filePath) { return readFile$1(filePath, { encoding: "utf8" }); } /** * 读取文件并解析为 JSON。 * @param filePath - 文件路径。 * @returns 读取结果。 */ async function readJsonFile$1(filePath) { return (await readTextFile$1(filePath)).andThen((contents) => { return (0, happy_rusty.tryResult)(JSON.parse, contents); }); } /** * 将数据序列化为 JSON 并写入文件。 * @param filePath - 文件路径。 * @param data - 要写入的数据。 * @returns 写入结果。 */ async function writeJsonFile$1(filePath, data) { return (0, happy_rusty.tryResult)(JSON.stringify, data).andThenAsync((text) => writeFile$1(filePath, text)); } function downloadFile$1(fileUrl, filePath, options) { const fileUrlRes = (0, src_std_internal_mod_ts.validateSafeUrl)(fileUrl); if (fileUrlRes.isErr()) return (0, src_std_internal_mod_ts.createFailedFetchTask)(fileUrlRes); if (typeof filePath === "string") { const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return (0, src_std_internal_mod_ts.createFailedFetchTask)(filePathRes); filePath = filePathRes.unwrap(); } else { options = filePath; filePath = void 0; } const { onProgress, headers, ...rest } = options ?? {}; let aborted = false; const future = new tiny_future.Future(); let task; const download = () => { task = wx.downloadFile({ ...rest, url: fileUrl, filePath, header: headers, async success(response) { if (aborted) { future.resolve((0, happy_rusty.Err)(createAbortError())); return; } const { statusCode } = response; if (statusCode >= 200 && statusCode < 300) { future.resolve((0, happy_rusty.Ok)(response)); return; } if (response.filePath) await remove$1(response.filePath); future.resolve((0, happy_rusty.Err)(new _happy_ts_fetch_t.FetchError(response.errMsg, statusCode))); }, fail(err) { future.resolve(aborted ? (0, happy_rusty.Err)(createAbortError()) : miniGameFailureToResult(err)); } }); if (typeof onProgress === "function") task.onProgressUpdate((progress) => { const { totalBytesExpectedToWrite, totalBytesWritten } = progress; onProgress(typeof totalBytesExpectedToWrite === "number" && typeof totalBytesWritten === "number" ? (0, happy_rusty.Ok)({ totalByteLength: totalBytesExpectedToWrite, completedByteLength: totalBytesWritten }) : (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Unknown download progress ${totalBytesWritten}/${totalBytesExpectedToWrite}`))); }); }; if (typeof filePath === "string") mkdir$1((0, src_std_path_mod_ts.dirname)(filePath)).then((mkdirRes) => { if (aborted) { future.resolve((0, happy_rusty.Err)(createAbortError())); return; } if (mkdirRes.isErr()) { future.resolve(mkdirRes.asErr()); return; } download(); }); else download(); return { abort() { aborted = true; task?.abort(); }, get aborted() { return aborted; }, get result() { return future.promise; } }; } /** * 文件上传。 * @param filePath - 需要上传的文件路径。 * @param fileUrl - 目标网络 URL。 * @param options - 可选参数。 * @returns 上传操作的异步结果。 */ function uploadFile$1(filePath, fileUrl, options) { const fileUrlRes = (0, src_std_internal_mod_ts.validateSafeUrl)(fileUrl); if (fileUrlRes.isErr()) return (0, src_std_internal_mod_ts.createFailedFetchTask)(fileUrlRes); const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return (0, src_std_internal_mod_ts.createFailedFetchTask)(filePathRes); filePath = filePathRes.unwrap(); const { headers, ...rest } = options ?? {}; let aborted = false; const future = new tiny_future.Future(); const task = wx.uploadFile({ name: (0, src_std_path_mod_ts.basename)(filePath), ...rest, url: fileUrl, filePath, header: headers, success(res) { future.resolve((0, happy_rusty.Ok)(res)); }, fail(err) { future.resolve(miniGameFailureToResult(err)); } }); return { abort() { aborted = true; task?.abort(); }, get aborted() { return aborted; }, get result() { return future.promise; } }; } /** * 解压 zip 文件。 * @param zipFilePath - 要解压的 zip 文件路径。 * @param destDir - 要解压到的目标文件夹路径。 * @returns 解压操作的异步结果。 */ async function unzip$1(zipFilePath, destDir) { const zipFilePathRes = validateAbsolutePath(zipFilePath); if (zipFilePathRes.isErr()) return zipFilePathRes.asErr(); zipFilePath = zipFilePathRes.unwrap(); const destDirRes = validateAbsolutePath(destDir); if (destDirRes.isErr()) return destDirRes.asErr(); destDir = destDirRes.unwrap(); return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().unzip)({ zipFilePath, targetPath: destDir })).and(happy_rusty.RESULT_VOID).orElse(fileErrorToResult); } /** * 从网络下载 zip 文件并解压。 * @param zipFileUrl - Zip 文件的网络地址。 * @param destDir - 要解压到的目标文件夹路径。 * @param options - 可选的下载参数。 * @returns 下载并解压操作的异步结果。 */ async function unzipFromUrl$1(zipFileUrl, destDir, options) { const destDirRes = validateAbsolutePath(destDir); if (destDirRes.isErr()) return destDirRes.asErr(); destDir = destDirRes.unwrap(); return (await downloadFile$1(zipFileUrl, options).result).andThenAsync(({ tempFilePath }) => { return unzip$1(tempFilePath, destDir); }); } async function zip$1(sourcePath, zipFilePath, options) { if (typeof zipFilePath === "string") { const zipFilePathRes = validateAbsolutePath(zipFilePath); if (zipFilePathRes.isErr()) return zipFilePathRes.asErr(); zipFilePath = zipFilePathRes.unwrap(); } else { options = zipFilePath; zipFilePath = void 0; } const statRes = await stat$2(sourcePath, { recursive: true }); if (statRes.isErr()) return statRes.asErr(); const statsArray = statRes.unwrap(); sourcePath = validateAbsolutePath(sourcePath).unwrap(); const sourceName = (0, src_std_path_mod_ts.basename)(sourcePath); const zippable = {}; if (statsArray.length === 1 && statsArray[0].stats.isFile()) { const readFileRes = await readFile$1(sourcePath); if (readFileRes.isErr()) return readFileRes; zippable[sourceName] = readFileRes.unwrap(); } else { const preserveRoot = options?.preserveRoot ?? true; if (preserveRoot) zippable[sourceName + src_std_path_mod_ts.SEPARATOR] = EMPTY_BYTES; const tasks = []; for (const { path, stats } of statRes.unwrap()) { if (!path) continue; const entryName = preserveRoot ? sourceName + src_std_path_mod_ts.SEPARATOR + path : path; if (stats.isFile()) tasks.push((async () => { return (await readFile$1(sourcePath + src_std_path_mod_ts.SEPARATOR + path)).map((data) => ({ entryName, data })); })()); else zippable[entryName + src_std_path_mod_ts.SEPARATOR] = EMPTY_BYTES; } if (tasks.length > 0) { const results = await Promise.all(tasks); for (const result of results) { if (result.isErr()) return result.asErr(); const { entryName, data } = result.unwrap(); zippable[entryName] = data; } } } if (Object.keys(zippable).length === 0) return createNothingToZipError(); return zipTo(zippable, zipFilePath); } async function zipFromUrl$1(sourceUrl, zipFilePath, options) { if (typeof zipFilePath === "string") { const zipFilePathRes = validateAbsolutePath(zipFilePath); if (zipFilePathRes.isErr()) return zipFilePathRes.asErr(); zipFilePath = zipFilePathRes.unwrap(); } else { options = zipFilePath; zipFilePath = void 0; } return (await downloadFile$1(sourceUrl, options).result).andThenAsync(async ({ tempFilePath }) => { const readFileRes = await readFile$1(tempFilePath); if (readFileRes.isErr()) return readFileRes; return zipTo({ [(0, src_std_path_mod_ts.basename)(tempFilePath)]: readFileRes.unwrap() }, zipFilePath); }); } /** * 将错误对象转换为 IOResult 类型。 */ function miniGameFailureToResult(error) { return (0, happy_rusty.Err)((0, src_std_internal_mod_ts.miniGameFailureToError)(error)); } /** * 创建一个 `AbortError` 错误。 */ function createAbortError() { const error = /* @__PURE__ */ new Error(); error.name = _happy_ts_fetch_t.ABORT_ERROR; return error; } /** * 复制文件。 */ async function copyFile(srcPath, destPath) { return (await (0, src_std_utils_mod_ts.asyncResultify)(getFs().copyFile)({ srcPath, destPath })).and(happy_rusty.RESULT_VOID).orElse(fileErrorToResult); } /** * 将 zippable 对象压缩为 zip 文件。 */ function zipTo(zippable, zipFilePath) { return (0, happy_rusty.tryResult)(() => (0, fflate_browser.zipSync)(zippable)).andThenAsync((bytesLike) => { const bytes = bytesLike; return zipFilePath ? writeFile$1(zipFilePath, bytes) : Promise.resolve((0, happy_rusty.Ok)(bytes)); }); } //#endregion //#region src/std/fs/web_fs_helpers.ts /** * @internal * 文件系统辅助函数。 */ /** * 将 `FileSystemHandleLike` 转换为小游戏 `Stats`。 * @param handleLike - 要转换的 `FileSystemHandleLike` 对象。 * @returns 小游戏的 `Stats` 对象。 */ function convertFileSystemHandleLikeToStats(handleLike) { const isFile = (0, happy_opfs.isFileHandleLike)(handleLike); return { isFile: () => isFile, isDirectory: () => !isFile, size: isFile ? handleLike.size : 0, lastModifiedTime: isFile ? handleLike.lastModified : 0, lastAccessedTime: 0, mode: 0 }; } /** * 将 `FileSystemHandle` 转换为小游戏 `Stats`。 * @param handle - 要转换的 `FileSystemHandle` 对象。 * @returns 小游戏的 `Stats` 对象。 */ async function convertFileSystemHandleToStats(handle) { const isFile = (0, happy_opfs.isFileHandle)(handle); let size = 0; let lastModified = 0; if (isFile) { const file = await handle.getFile(); ({size, lastModified} = file); } return { isFile: () => isFile, isDirectory: () => !isFile, size, lastModifiedTime: lastModified, lastAccessedTime: 0, mode: 0 }; } /** * 将 Web 端的读取目录结果转换为小游戏端的读取目录结果。 * @param dirPath - 要读取的目录路径。 * @returns 目录内容路径数组。 */ async function webToMinaReadDir(dirPath) { return (await (0, happy_opfs.readDir)(dirPath)).andTryAsync(async (entries) => { if (typeof Array.fromAsync === "function") return Array.fromAsync(entries, ({ path }) => path); const items = []; for await (const { path } of entries) items.push(path); return items; }); } /** * 将 Web 端的 stat 结果转换为小游戏端的 stat 结果。 * @param path - 要获取状态的路径。 * @param options - 可选的 stat 选项。 * @returns 文件或目录的状态信息。 */ async function webToMinaStat(path, options) { const statRes = await (0, happy_opfs.stat)(path); if (statRes.isErr()) return statRes.asErr(); const entryStatsRes = await (0, happy_rusty.tryAsyncResult)(convertFileSystemHandleToStats(statRes.unwrap())); if (entryStatsRes.isErr()) return entryStatsRes; if (!options?.recursive) return entryStatsRes; const entryStats = entryStatsRes.unwrap(); if (entryStats.isFile()) return (0, happy_rusty.Ok)([{ path: "", stats: entryStats }]); return (await (0, happy_opfs.readDir)(path)).andTryAsync(async (entries) => { const tasks = [Promise.resolve({ path: "", stats: entryStats })]; for await (const { path, handle } of entries) tasks.push((async () => { const stats = await convertFileSystemHandleToStats(handle); return { path, stats }; })()); return Promise.all(tasks); }); } /** * 将 Web 端的读取目录结果转换为小游戏端的读取目录结果。 * @param dirPath - 要读取的目录路径。 * @returns 目录内容路径数组。 */ function webToMinaReadDirSync(dirPath) { return (0, happy_opfs.readDirSync)(dirPath).map((entries) => { return entries.map(({ path }) => path); }); } /** * 将 Web 端的 stat 结果转换为小游戏端的 stat 结果。 * @param path - 要获取状态的路径。 * @param options - 可选的 stat 选项。 * @returns 文件或目录的状态信息。 */ function webToMinaStatSync(path, options) { const statRes = (0, happy_opfs.statSync)(path); if (statRes.isErr()) return statRes.asErr(); const entryStats = convertFileSystemHandleLikeToStats(statRes.unwrap()); if (!options?.recursive) return (0, happy_rusty.Ok)(entryStats); if (entryStats.isFile()) return (0, happy_rusty.Ok)([{ path: "", stats: entryStats }]); return (0, happy_opfs.readDirSync)(path).map((entries) => { const statsArr = entries.map(({ path, handle }) => ({ path, stats: convertFileSystemHandleLikeToStats(handle) })); statsArr.unshift({ path: "", stats: entryStats }); return statsArr; }); } //#endregion //#region src/std/fs/fs_async.ts /** * 递归创建文件夹,相当于 `mkdir -p`。 * @param dirPath - 将要创建的目录的路径。 * @returns 创建成功返回的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await mkdir('/path/to/dir'); * if (result.isOk()) { * console.log('目录创建成功'); * } * ``` */ function mkdir(dirPath) { return (IS_MINA ? mkdir$1 : happy_opfs.mkdir)(dirPath); } /** * 移动或重命名文件或目录。 * @param srcPath - 原始路径。 * @param destPath - 新路径。 * @returns 操作成功返回的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await move('/old/path', '/new/path'); * if (result.isOk()) { * console.log('移动成功'); * } * ``` */ function move(srcPath, destPath) { return (IS_MINA ? move$1 : happy_opfs.move)(srcPath, destPath); } /** * 异步读取指定目录下的所有文件和子目录。 * @param dirPath - 需要读取的目录路径。 * @returns 包含目录内容的字符串数组的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await readDir('/path/to/dir'); * if (result.isOk()) { * console.log(result.unwrap()); // ['file1.txt', 'file2.txt', 'subdir'] * } * ``` */ async function readDir(dirPath) { return (IS_MINA ? readDir$2 : webToMinaReadDir)(dirPath); } /** * 读取文件内容,可选地指定编码和返回类型。 * @template T - 返回内容的类型。 * @param filePath - 文件路径。 * @param options - 可选的读取选项。 * @returns 包含文件内容的异步操作结果。 */ function readFile(filePath, options) { return IS_MINA ? readFile$1(filePath, options) : (0, happy_opfs.readFile)(filePath, options); } /** * 删除文件或目录。 * @param path - 要删除的文件或目录的路径。 * @returns 删除成功返回的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await remove('/path/to/file.txt'); * if (result.isOk()) { * console.log('删除成功'); * } * ``` */ function remove(path) { return (IS_MINA ? remove$1 : happy_opfs.remove)(path); } /** * 获取文件或目录的状态信息。 * @param path - 文件或目录的路径。 * @param options - 可选选项,包含 recursive 可递归获取目录下所有文件状态。 * @returns 包含状态信息的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await stat('/path/to/file.txt'); * if (result.isOk()) { * const stats = result.unwrap(); * console.log(stats.isFile()); // true * } * ``` */ async function stat(path, options) { return (IS_MINA ? stat$2 : webToMinaStat)(path, options); } /** * 写入文件,文件不存在则创建,同时创建对应目录。 * @param filePath - 文件路径。 * @param contents - 要写入的内容,支持 ArrayBuffer 和 string(需确保是 UTF-8 编码)。 * @param options - 可选写入选项。 * @returns 写入成功返回的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await writeFile('/path/to/file.txt', 'Hello, World!'); * if (result.isOk()) { * console.log('写入成功'); * } * ``` */ function writeFile(filePath, contents, options) { return (IS_MINA ? writeFile$1 : happy_opfs.writeFile)(filePath, contents, options); } /** * 向文件追加内容。 * @param filePath - 文件路径。 * @param contents - 要追加的内容。 * @param options - 可选的追加选项。 * @returns 追加成功返回的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await appendFile('/path/to/file.txt', '\nNew content'); * if (result.isOk()) { * console.log('追加成功'); * } * ``` */ function appendFile(filePath, contents, options) { return (IS_MINA ? appendFile$1 : happy_opfs.appendFile)(filePath, contents, options); } /** * 复制文件或文件夹。 * @param srcPath - 源文件或文件夹路径。 * @param destPath - 目标文件或文件夹路径。 * @returns 操作的异步结果。 * @since 1.0.0 * @example * ```ts * const result = await copy('/src/file.txt', '/dest/file.txt'); * if (result.isOk()) { * console.log('复制成功'); * } * ``` */ function copy(srcPath, destPath) { return (IS_MINA ? copy$1 : happy_opfs.copy)(srcPath, destPath); } /** * 检查指定路径的文件或目录是否存在。 * @param path - 文件或目录的路径。 * @param options - 可选的检查选项。 * @returns 存在返回 true 的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await exists('/path/to/file.txt'); * if (result.isOk() && result.unwrap()) { * console.log('文件存在'); * } * ``` */ function exists(path, options) { return (IS_MINA ? exists$1 : happy_opfs.exists)(path, options); } /** * 清空指定目录下的所有文件和子目录。 * @param dirPath - 目录路径。 * @returns 清空成功返回的异步操作结果。 * @since 1.0.1 * @example * ```ts * const result = await emptyDir('/path/to/dir'); * if (result.isOk()) { * console.log('目录已清空'); * } * ``` */ function emptyDir(dirPath) { return (IS_MINA ? emptyDir$1 : happy_opfs.emptyDir)(dirPath); } /** * 读取 JSON 文件并解析为对象。 * @typeParam T - JSON 解析后的类型。 * @param filePath - 文件路径。 * @returns 解析后的对象。 * @since 1.6.0 * @example * ```ts * const result = await readJsonFile<{ name: string }>('/path/to/config.json'); * if (result.isOk()) { * console.log(result.unwrap().name); * } * ``` */ function readJsonFile(filePath) { return (IS_MINA ? readJsonFile$1 : happy_opfs.readJsonFile)(filePath); } /** * 读取文本文件的内容。 * @param filePath - 文件路径。 * @returns 包含文件文本内容的异步操作结果。 * @since 1.0.0 * @example * ```ts * const result = await readTextFile('/path/to/file.txt'); * if (result.isOk()) { * console.log(result.unwrap()); * } * ``` */ function readTextFile(filePath) { return (IS_MINA ? readTextFile$1 : happy_opfs.readTextFile)(filePath); } /** * 将数据序列化为 JSON 并写入文件。 * @typeParam T - 要写入数据的类型。 * @param filePath - 文件路径。 * @param data - 要写入的数据。 * @returns 写入操作的异步结果。 * @since 2.0.0 * @example * ```ts * const result = await writeJsonFile('/path/to/config.json', { name: 'test' }); * if (result.isOk()) { * console.log('写入成功'); * } * ``` */ function writeJsonFile(filePath, data) { return (IS_MINA ? writeJsonFile$1 : happy_opfs.writeJsonFile)(filePath, data); } function downloadFile(fileUrl, filePath, options) { if (typeof filePath === "string") return IS_MINA ? downloadFile$1(fileUrl, filePath, options) : (0, happy_opfs.downloadFile)(fileUrl, filePath, options); else return IS_MINA ? downloadFile$1(fileUrl, filePath) : (0, happy_opfs.downloadFile)(fileUrl, filePath); } /** * 上传本地文件到服务器。 * @param filePath - 需要上传的文件路径。 * @param fileUrl - 目标服务器的 URL。 * @param options - 可选的请求初始化参数。 * @returns 上传操作的 FetchTask。 * @since 1.0.0 * @example * ```ts * const task = uploadFile('/path/to/file.txt', 'https://example.com/upload'); * const result = await task.result; * if (result.isOk()) { * console.log('上传成功'); * } * ``` */ function uploadFile(filePath, fileUrl, options) { return IS_MINA ? uploadFile$1(filePath, fileUrl, options) : (0, happy_opfs.uploadFile)(filePath, fileUrl, options); } /** * 解压 zip 文件。 * @param zipFilePath - 要解压的 zip 文件路径。 * @param targetPath - 要解压到的目标文件夹路径。 * @returns 解压操作的异步结果。 * @since 1.3.0 * @example * ```ts * const result = await unzip('/path/to/archive.zip', '/path/to/output'); * if (result.isOk()) { * console.log('解压成功'); * } * ``` */ function unzip(zipFilePath, targetPath) { return (IS_MINA ? unzip$1 : happy_opfs.unzip)(zipFilePath, targetPath); } /** * 从网络下载 zip 文件并解压。 * @param zipFileUrl - Zip 文件的网络地址。 * @param targetPath - 要解压到的目标文件夹路径。 * @param options - 可选的下载参数。 * @returns 下载并解压操作的异步结果。 * @since 1.4.0 * @example * ```ts * const result = await unzipFromUrl('https://example.com/archive.zip', '/path/to/output'); * if (result.isOk()) { * console.log('下载并解压成功'); * } * ``` */ function unzipFromUrl(zipFileUrl, targetPath, options) { return (IS_MINA ? unzipFromUrl$1 : happy_opfs.unzipFromUrl)(zipFileUrl, targetPath, options); } function zip(sourcePath, zipFilePath, options) { if (typeof zipFilePath === "string") return (IS_MINA ? zip$1 : happy_opfs.zip)(sourcePath, zipFilePath, options); else return (IS_MINA ? zip$1 : happy_opfs.zip)(sourcePath, zipFilePath); } function zipFromUrl(sourceUrl, zipFilePath, options) { if (typeof zipFilePath === "string") return IS_MINA ? zipFromUrl$1(sourceUrl, zipFilePath, options) : (0, happy_opfs.zipFromUrl)(sourceUrl, zipFilePath, options); else return IS_MINA ? zipFromUrl$1(sourceUrl, zipFilePath) : (0, happy_opfs.zipFromUrl)(sourceUrl, zipFilePath); } //#endregion //#region src/std/fs/mina_fs_sync.ts /** * @internal * 小游戏平台的同步文件系统操作实现。 */ /** * `mkdir` 的同步版本。 * @param dirPath - 要创建的目录路径。 * @returns 操作结果。 */ function mkdirSync$1(dirPath) { const dirPathRes = validateAbsolutePath(dirPath); if (dirPathRes.isErr()) return dirPathRes.asErr(); dirPath = dirPathRes.unwrap(); if (dirPath === getUsrPath()) return happy_rusty.RESULT_VOID; const statRes = statSync$1(dirPath); if (statRes.isOk()) { if (statRes.unwrap().isFile()) return createDirIsFileError(dirPath); return happy_rusty.RESULT_VOID; } return trySyncOp(() => getFs().mkdirSync(dirPath, true), fileErrorToMkdirResult); } /** * `move` 的同步版本。 * @param srcPath - 源路径。 * @param destPath - 目标路径。 * @returns 操作结果。 */ function moveSync$1(srcPath, destPath) { const srcPathRes = validateAbsolutePath(srcPath); if (srcPathRes.isErr()) return srcPathRes.asErr(); srcPath = srcPathRes.unwrap(); const destPathRes = validateAbsolutePath(destPath); if (destPathRes.isErr()) return destPathRes.asErr(); destPath = destPathRes.unwrap(); return trySyncOp(() => getFs().renameSync(srcPath, destPath)); } /** * `readDir` 的同步版本。 * @param dirPath - 要读取的目录路径。 * @returns 目录中的文件和子目录名称数组。 */ function readDirSync$1(dirPath) { const dirPathRes = validateReadablePath(dirPath); if (dirPathRes.isErr()) return dirPathRes.asErr(); dirPath = dirPathRes.unwrap(); return trySyncOp(() => getFs().readdirSync(dirPath)); } function readFileSync$1(filePath, options) { const filePathRes = validateReadablePath(filePath); if (filePathRes.isErr()) return filePathRes.asErr(); filePath = filePathRes.unwrap(); const encoding = getReadFileEncoding(options); return trySyncOp(() => { const data = getFs().readFileSync(filePath, encoding); return typeof data === "string" ? data : new Uint8Array(data); }); } /** * `remove` 的同步版本。 * @param path - 要删除的文件或目录路径。 * @returns 操作结果。 */ function removeSync$1(path) { const statRes = statSync$1(path); if (statRes.isErr()) return isNotFoundError(statRes.unwrapErr()) ? happy_rusty.RESULT_VOID : statRes.asErr(); path = validateAbsolutePath(path).unwrap(); return trySyncOp(() => { if (statRes.unwrap().isDirectory()) getFs().rmdirSync(path, true); else getFs().unlinkSync(path); }, fileErrorToRemoveResult); } function statSync$1(path, options) { const pathRes = validateReadablePath(path); if (pathRes.isErr()) return pathRes.asErr(); path = pathRes.unwrap(); const { recursive = false } = options ?? {}; return trySyncOp(() => getFs().statSync(path, recursive)).map((x) => normalizeStats(x, recursive)); } /** * `writeFile` 的同步版本。 * @param filePath - 要写入的文件路径。 * @param contents - 要写入的内容。 * @param options - 写入选项。 * @returns 操作结果。 */ function writeFileSync$1(filePath, contents, options) { const { append = false, create = true } = options ?? {}; const fs = getFs(); let writeMethod = fs.writeFileSync; if (append) { const existsRes = existsSync$1(filePath); if (existsRes.isErr()) return existsRes.asErr(); if (existsRes.unwrap()) writeMethod = fs.appendFileSync; else { if (!create) return createFileNotExistsError(filePath); writeMethod = fs.writeFileSync; } } const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return filePathRes.asErr(); filePath = filePathRes.unwrap(); if (create && writeMethod === fs.writeFileSync) { const result = mkdirSync$1((0, src_std_path_mod_ts.dirname)(filePath)); if (result.isErr()) return result; } const contentsRes = getWriteFileContents(contents); if (contentsRes.isErr()) return contentsRes.asErr(); const { data, encoding } = contentsRes.unwrap(); return trySyncOp(() => writeMethod(filePath, data, encoding)); } /** * `appendFile` 的同步版本。 * @param filePath - 文件路径。 * @param contents - 要追加的内容。 * @param options - 可选的追加选项。 * @returns 操作结果。 */ function appendFileSync$1(filePath, contents, options) { return writeFileSync$1(filePath, contents, { append: true, create: options?.create ?? true }); } /** * `copy` 的同步版本。 * @param srcPath - 源路径。 * @param destPath - 目标路径。 * @returns 操作结果。 */ function copySync$1(srcPath, destPath) { const destPathRes = validateAbsolutePath(destPath); if (destPathRes.isErr()) return destPathRes.asErr(); destPath = destPathRes.unwrap(); const statRes = statSync$1(srcPath, { recursive: true }); if (statRes.isErr()) return statRes.asErr(); srcPath = validateAbsolutePath(srcPath).unwrap(); for (const { path, stats } of statRes.unwrap()) { let copyRes; if (!path) if (stats.isDirectory()) copyRes = mkdirSync$1(destPath); else copyRes = mkdirSync$1((0, src_std_path_mod_ts.dirname)(destPath)).andThen(() => { return copyFileSync(srcPath, destPath); }); else { const srcEntryPath = srcPath + src_std_path_mod_ts.SEPARATOR + path; const destEntryPath = destPath + src_std_path_mod_ts.SEPARATOR + path; copyRes = stats.isDirectory() ? mkdirSync$1(destEntryPath) : copyFileSync(srcEntryPath, destEntryPath); } if (copyRes.isErr()) return copyRes; } return happy_rusty.RESULT_VOID; } /** * `exists` 的同步版本。 * @param path - 文件或目录路径。 * @param options - 检查选项。 * @returns 是否存在。 */ function existsSync$1(path, options) { const optionsRes = validateExistsOptions(options); if (optionsRes.isErr()) return optionsRes.asErr(); const { isDirectory = false, isFile = false } = options ?? {}; if (!isDirectory && !isFile) return accessExistsSync(path); return getExistsResult(statSync$1(path), options); } /** * `emptyDir` 的同步版本。 * @param dirPath - 要清空的目录路径。 * @returns 操作结果。 */ function emptyDirSync$1(dirPath) { const readDirRes = readDirSync$1(dirPath); if (readDirRes.isErr()) return isNotFoundError(readDirRes.unwrapErr()) ? mkdirSync$1(dirPath) : readDirRes.asErr(); dirPath = validateAbsolutePath(dirPath).unwrap(); for (const name of readDirRes.unwrap()) { const removeRes = removeSync$1(dirPath + src_std_path_mod_ts.SEPARATOR + name); if (removeRes.isErr()) return removeRes.asErr(); } return happy_rusty.RESULT_VOID; } /** * `readTextFile` 的同步版本。 * @param filePath - 要读取的文件路径。 * @returns 文件的文本内容。 */ function readTextFileSync$1(filePath) { return readFileSync$1(filePath, { encoding: "utf8" }); } /** * `readJsonFile` 的同步版本。 * @param filePath - 要读取的 JSON 文件路径。 * @returns 解析后的 JSON 对象。 */ function readJsonFileSync$1(filePath) { return readTextFileSync$1(filePath).andThen((contents) => { return (0, happy_rusty.tryResult)(JSON.parse, contents); }); } /** * `writeJsonFile` 的同步版本。 * @param filePath - 要写入的文件路径。 * @param data - 要序列化并写入的数据。 * @returns 操作结果。 */ function writeJsonFileSync$1(filePath, data) { return (0, happy_rusty.tryResult)(JSON.stringify, data).andThen((text) => writeFileSync$1(filePath, text)); } /** * `unzip` 的同步版本。 * @param zipFilePath - 要解压的 ZIP 文件路径。 * @param destDir - 解压目标目录。 * @returns 操作结果。 */ function unzipSync$1(zipFilePath, destDir) { const destDirRes = validateAbsolutePath(destDir); if (destDirRes.isErr()) return destDirRes.asErr(); destDir = destDirRes.unwrap(); const readFileRes = readFileSync$1(zipFilePath); if (readFileRes.isErr()) return readFileRes.asErr(); const bytes = readFileRes.unwrap(); const unzippedRes = (0, happy_rusty.tryResult)(() => (0, fflate_browser.unzipSync)(bytes)); if (unzippedRes.isErr()) return unzippedRes.asErr(); const unzipped = unzippedRes.unwrap(); for (const path in unzipped) if (path.at(-1) === src_std_path_mod_ts.SEPARATOR) { const mkdirRes = mkdirSync$1(destDir + src_std_path_mod_ts.SEPARATOR + path.slice(1)); if (mkdirRes.isErr()) return mkdirRes.asErr(); } else { const writeFileRes = writeFileSync$1(destDir + src_std_path_mod_ts.SEPARATOR + path, unzipped[path]); if (writeFileRes.isErr()) return writeFileRes.asErr(); } return happy_rusty.RESULT_VOID; } function zipSync$1(sourcePath, zipFilePath, options) { if (typeof zipFilePath === "string") { const zipFilePathRes = validateAbsolutePath(zipFilePath); if (zipFilePathRes.isErr()) return zipFilePathRes.asErr(); zipFilePath = zipFilePathRes.unwrap(); } else { options = zipFilePath; zipFilePath = void 0; } const statRes = statSync$1(sourcePath, { recursive: true }); if (statRes.isErr()) return statRes.asErr(); const statsArray = statRes.unwrap(); sourcePath = validateAbsolutePath(sourcePath).unwrap(); const sourceName = (0, src_std_path_mod_ts.basename)(sourcePath); const zippable = {}; if (statsArray.length === 1 && statsArray[0].stats.isFile()) { const readFileRes = readFileSync$1(sourcePath); if (readFileRes.isErr()) return readFileRes; zippable[sourceName] = readFileRes.unwrap(); } else { const preserveRoot = options?.preserveRoot ?? true; if (preserveRoot) zippable[sourceName + src_std_path_mod_ts.SEPARATOR] = EMPTY_BYTES; for (const { path, stats } of statsArray) { if (!path) continue; const entryName = preserveRoot ? sourceName + src_std_path_mod_ts.SEPARATOR + path : path; if (stats.isFile()) { const readFileRes = readFileSync$1(sourcePath + src_std_path_mod_ts.SEPARATOR + path); if (readFileRes.isErr()) return readFileRes; zippable[entryName] = readFileRes.unwrap(); } else zippable[entryName + src_std_path_mod_ts.SEPARATOR] = EMPTY_BYTES; } } if (Object.keys(zippable).length === 0) return createNothingToZipError(); return (0, happy_rusty.tryResult)(() => (0, fflate_browser.zipSync)(zippable)).andThen((bytesLike) => { const bytes = bytesLike; return zipFilePath ? writeFileSync$1(zipFilePath, bytes) : (0, happy_rusty.Ok)(bytes); }); } /** * 安全地调用同步接口。 */ function trySyncOp(op, errToResult = fileErrorToResult) { return (0, happy_rusty.tryResult)(op).orElse(errToResult); } /** * 复制单个文件。 */ function copyFileSync(srcPath, destPath) { return trySyncOp(() => getFs().copyFileSync(srcPath, destPath)); } //#endregion //#region src/std/fs/fs_sync.ts /** * `mkdir` 的同步版本,递归创建文件夹。 * @param dirPath - 将要创建的目录的路径。 * @returns 创建成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = mkdirSync('/path/to/dir'); * if (result.isOk()) { * console.log('目录创建成功'); * } * ``` */ function mkdirSync(dirPath) { return (IS_MINA ? mkdirSync$1 : happy_opfs.mkdirSync)(dirPath); } /** * `move` 的同步版本,移动或重命名文件或目录。 * @param srcPath - 原始路径。 * @param destPath - 新路径。 * @returns 操作成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = moveSync('/old/path', '/new/path'); * if (result.isOk()) { * console.log('移动成功'); * } * ``` */ function moveSync(srcPath, destPath) { return (IS_MINA ? moveSync$1 : happy_opfs.moveSync)(srcPath, destPath); } /** * `readDir` 的同步版本,读取指定目录下的所有文件和子目录。 * @param dirPath - 需要读取的目录路径。 * @returns 包含目录内容的字符串数组的操作结果。 * @since 1.1.0 * @example * ```ts * const result = readDirSync('/path/to/dir'); * if (result.isOk()) { * console.log(result.unwrap()); // ['file1.txt', 'file2.txt'] * } * ``` */ function readDirSync(dirPath) { return (IS_MINA ? readDirSync$1 : webToMinaReadDirSync)(dirPath); } function readFileSync(filePath, options) { return IS_MINA ? readFileSync$1(filePath, options) : (0, happy_opfs.readFileSync)(filePath, options); } /** * `remove` 的同步版本,删除文件或目录。 * @param path - 要删除的文件或目录的路径。 * @returns 删除成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = removeSync('/path/to/file.txt'); * if (result.isOk()) { * console.log('删除成功'); * } * ``` */ function removeSync(path) { return (IS_MINA ? removeSync$1 : happy_opfs.removeSync)(path); } function statSync(path, options) { return (IS_MINA ? statSync$1 : webToMinaStatSync)(path, options); } /** * `writeFile` 的同步版本,写入文件。 * @param filePath - 文件路径。 * @param contents - 要写入的内容。 * @returns 写入成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = writeFileSync('/path/to/file.txt', 'Hello, World!'); * if (result.isOk()) { * console.log('写入成功'); * } * ``` */ function writeFileSync(filePath, contents) { return (IS_MINA ? writeFileSync$1 : happy_opfs.writeFileSync)(filePath, contents); } /** * `appendFile` 的同步版本,向文件追加内容。 * @param filePath - 文件路径。 * @param contents - 要追加的内容。 * @param options - 可选的追加选项。 * @returns 追加成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = appendFileSync('/path/to/file.txt', '\nNew content'); * if (result.isOk()) { * console.log('追加成功'); * } * ``` */ function appendFileSync(filePath, contents, options) { return (IS_MINA ? appendFileSync$1 : happy_opfs.appendFileSync)(filePath, contents, options); } /** * `copy` 的同步版本,复制文件或文件夹。 * @param srcPath - 源文件或文件夹路径。 * @param destPath - 目标文件或文件夹路径。 * @returns 操作的结果。 * @since 1.1.0 * @example * ```ts * const result = copySync('/src/file.txt', '/dest/file.txt'); * if (result.isOk()) { * console.log('复制成功'); * } * ``` */ function copySync(srcPath, destPath) { return (IS_MINA ? copySync$1 : happy_opfs.copySync)(srcPath, destPath); } /** * `exists` 的同步版本,检查指定路径的文件或目录是否存在。 * @param path - 文件或目录的路径。 * @param options - 可选的检查选项。 * @returns 存在返回 true 的操作结果。 * @since 1.1.0 * @example * ```ts * const result = existsSync('/path/to/file.txt'); * if (result.isOk() && result.unwrap()) { * console.log('文件存在'); * } * ``` */ function existsSync(path, options) { return (IS_MINA ? existsSync$1 : happy_opfs.existsSync)(path, options); } /** * `emptyDir` 的同步版本,清空指定目录下的所有文件和子目录。 * @param dirPath - 目录路径。 * @returns 清空成功返回的操作结果。 * @since 1.1.0 * @example * ```ts * const result = emptyDirSync('/path/to/dir'); * if (result.isOk()) { * console.log('目录已清空'); * } * ``` */ function emptyDirSync(dirPath) { return (IS_MINA ? emptyDirSync$1 : happy_opfs.emptyDirSync)(dirPath); } /** * `readJsonFile` 的同步版本,读取 JSON 文件并解析为对象。 * @typeParam T - JSON 解析后的类型。 * @param filePath - 文件路径。 * @returns 解析后的对象。 * @since 1.6.0 * @example * ```ts * const result = readJsonFi