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,695 lines 168 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 __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); return target; }; 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_rusty = require("happy-rusty"); let _happy_ts_fetch_t = require("@happy-ts/fetch-t"); let tiny_future = require("tiny-future"); let happy_opfs = require("happy-opfs"); happy_opfs = __toESM(happy_opfs, 1); let fflate_browser = require("fflate/browser"); let happy_codec = require("happy-codec"); let rsa_oaep_encryption = require("rsa-oaep-encryption"); //#region src/macros/env.ts /** * 如果在小游戏环境中返回 true,否则返回 false。 */ const IS_MINA = __MINIGAME_STD_MINA__; //#endregion //#region src/std/internal/constants.ts /** * @internal * 内部常量。 */ /** * 异步返回 void 的成功结果常量。 */ const ASYNC_RESULT_VOID = /* @__PURE__ */ Promise.resolve(happy_rusty.RESULT_VOID); //#endregion //#region src/std/internal/helpers.ts /** * 将小游戏失败回调的结果转换为 `Error` 类型。 * * 如果是异步 API 的 `fail` 回调返回的结果通常是 `WechatMinigame.GeneralCallbackResult` 或者变体类型, * 如果是同步 API throw 的异常通常是一个类似 `Error` 的类型。 * @param error - 小游戏错误对象。 * @returns 转换后的 `Error` 对象。 */ function miniGameFailureToError(error) { return error instanceof Error ? error : new Error(error.errMsg ?? error.message); } /** * 将 BufferSource 转换为 Uint8Array。 * @param data - 需要转换的 BufferSource。 * @returns Uint8Array。 */ function bufferSourceToBytes(data) { if (data instanceof Uint8Array) return data; if (data instanceof ArrayBuffer) return new Uint8Array(data); if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); throw new TypeError("Input argument must be an ArrayBuffer or ArrayBufferView"); } /** * 将 BufferSource 转换为 ArrayBuffer。 * @param data - 需要转换的 BufferSource。 * @returns ArrayBuffer。 */ function bufferSourceToAb(data) { if (data instanceof ArrayBuffer) return data; if (ArrayBuffer.isView(data)) return data.byteOffset === 0 ? data.buffer : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); throw new TypeError("Input argument must be an ArrayBuffer or ArrayBufferView"); } /** * 创建一个已失败的 FetchTask 对象。 * @param errResult - 错误结果。 * @returns 返回一个已完成的失败 FetchTask。 */ function createFailedFetchTask(errResult) { return { abort() {}, get aborted() { return false; }, get result() { return Promise.resolve(errResult.asErr()); } }; } //#endregion //#region src/std/internal/validations.ts /** * @internal * 断言相关辅助函数。 */ /** * 验证传入的值是否为正整数。 * @param input - 需要验证的值。 * @param name - 参数名称,用于错误信息。 * @returns 验证结果,如果不是数字则返回包含 TypeError 的 Err,如果不是正整数则返回包含 Error 的 Err。 */ function validatePositiveInteger(input, name) { if (typeof input !== "number") return (0, happy_rusty.Err)(/* @__PURE__ */ new TypeError(`Param '${name}' must be a number but received ${typeof input}`)); if (input <= 0 || !Number.isInteger(input)) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Param '${name}' must be a positive integer but received ${input}`)); return happy_rusty.RESULT_VOID; } /** * 验证传入的值是否为字符串。 * @param str - 需要验证的值。 * @param name - 参数名称,用于错误信息。 * @returns 验证结果,如果不是字符串则返回包含 TypeError 的 Err。 */ function validateString(str, name) { if (typeof str !== "string") return (0, happy_rusty.Err)(/* @__PURE__ */ new TypeError(`Param '${name}' must be a string but received ${typeof str}`)); return happy_rusty.RESULT_VOID; } /** * 验证传入的 URL 是否为 `https` 协议。 * @param url - 需要验证的 URL 字符串。 * @returns 验证结果,如果不是 https 协议则返回 Err。 */ function validateSafeUrl(url) { return validateString(url, "url").andThen(() => { if (!url.startsWith("https://")) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Param url must start with https:// but received ${url}`)); return happy_rusty.RESULT_VOID; }); } /** * 验证传入的 WebSocket URL 是否为 `wss` 协议。 * @param socketUrl - 需要验证的 WebSocket URL 字符串。 * @returns 验证结果,如果不是 wss 协议则返回 Err。 */ function validateSafeSocketUrl(socketUrl) { return validateString(socketUrl, "socketUrl").andThen(() => { if (!socketUrl.startsWith("wss://")) return (0, happy_rusty.Err)(/* @__PURE__ */ new Error(`Param socketUrl must start with wss:// but received ${socketUrl}`)); return happy_rusty.RESULT_VOID; }); } //#endregion //#region src/std/fetch/mina_fetch.ts /** * @internal * 小游戏平台的 HTTP 请求实现。 */ /** * 发起一个网络请求,根据初始化配置返回对应类型的 FetchTask。 * @typeParam T - 预期的响应数据类型。 * @param url - 请求的 URL 地址。 * @param init - 请求的初始化配置。 * @returns 根据配置返回 FetchTask。 */ function minaFetch(url, init) { const urlRes = validateSafeUrl(url); if (urlRes.isErr()) return createFailedFetchTask(urlRes); const { responseType, onChunk, ...rest } = init ?? {}; let aborted = false; const future = new tiny_future.Future(); const options = { ...rest, url, success(res) { const { statusCode } = res; if (statusCode >= 200 && statusCode < 300) future.resolve((0, happy_rusty.Ok)(res.data)); else future.resolve((0, happy_rusty.Err)(new _happy_ts_fetch_t.FetchError(res.errMsg, statusCode))); }, fail(err) { const error = miniGameFailureToError(err); const { errMsg } = err; if (errMsg.includes("abort")) error.name = _happy_ts_fetch_t.ABORT_ERROR; else if (errMsg.includes("timeout")) error.name = _happy_ts_fetch_t.TIMEOUT_ERROR; future.resolve((0, happy_rusty.Err)(error)); } }; if (responseType === "arraybuffer") options.responseType = responseType; else if (responseType === "json") options.dataType = responseType; else { options.responseType = responseType; options.dataType = "其他"; } const task = wx.request(options); if (typeof onChunk === "function") task.onChunkReceived((res) => { onChunk(new Uint8Array(res.data)); }); return { abort() { aborted = true; task.abort(); }, get aborted() { return aborted; }, get result() { return future.promise; } }; } //#endregion //#region src/std/fetch/mod.ts /** * 网络请求模块,提供可中断的 fetch 请求功能,支持 text、JSON、ArrayBuffer 等响应类型。 * @module fetch */ /** * 发起一个网络请求,根据初始化配置返回对应类型的 FetchTask。 * @typeParam T - 预期的响应数据类型。 * @param url - 请求的 URL 地址。 * @param init - 请求的初始化配置。 * @returns FetchTask。 * @since 1.0.0 * @example * ```ts * // 发起 POST 请求 * const task = fetchT('https://api.example.com/submit', { * method: 'POST', * headers: { 'Content-Type': 'application/json' }, * body: JSON.stringify({ key: 'value' }), * responseType: 'json', * }); * const result = await task.result; * ``` */ function fetchT(url, init) { const defaultInit = init ?? {}; defaultInit.responseType ??= "text"; if (IS_MINA) { const { body, headers, ...rest } = defaultInit; if (body != null) rest.data = typeof body === "string" || isPlainObject(body) ? body : bufferSourceToAb(body); if (headers !== void 0) rest.header = headers; return minaFetch(url, rest); } const { body, ...rest } = defaultInit; const webInit = { ...rest, body, abortable: true }; if (isPlainObject(body)) { webInit.body = JSON.stringify(body); const headers = new Headers(webInit.headers); headers.set("Content-Type", "application/json"); webInit.headers = headers; } return (0, _happy_ts_fetch_t.fetchT)(url, webInit); } /** * 判断值是否为普通对象(非 string、非 BufferSource)。 */ function isPlainObject(value) { return value != null && typeof value === "object" && !ArrayBuffer.isView(value) && !(value instanceof ArrayBuffer); } //#endregion //#region src/std/path/mod.ts var path_exports = /* @__PURE__ */ __exportAll({ SEPARATOR: () => "/", basename: () => basename, dirname: () => dirname, normalize: () => normalize }); /** * POSIX 路径工具模块,提供 basename、dirname、normalize 等常用路径操作。 * 仅处理 string 路径,不涉及 URL,确保小游戏平台兼容。 * @module path */ const CHAR_FORWARD_SLASH = 47; const CHAR_DOT = 46; /** * 提取路径的最后一个片段(文件名)。 * @param path - 要处理的路径字符串。 * @param suffix - 可选的后缀,如果文件名以此结尾则去除。 * @returns 路径中的文件名部分。 * @since 2.4.0 * @example * ```ts * import { path } from 'minigame-std'; * * path.basename('/usr/local/file.txt'); // 'file.txt' * path.basename('/usr/local/file.txt', '.txt'); // 'file' * path.basename('/usr/local/'); // 'local' * ``` */ function basename(path, suffix) { if (path.length === 0) return ""; let end = path.length; while (end > 1 && path.charCodeAt(end - 1) === CHAR_FORWARD_SLASH) end--; if (end === 1 && path.charCodeAt(0) === CHAR_FORWARD_SLASH) return "/"; let start = 0; for (let i = end - 1; i >= 0; i--) if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { start = i + 1; break; } let base = path.slice(start, end); if (suffix && suffix.length < base.length && base.endsWith(suffix)) base = base.slice(0, -suffix.length); return base; } /** * 提取路径的目录部分。 * @param path - 要处理的路径字符串。 * @returns 路径中的目录部分。 * @since 2.4.0 * @example * ```ts * import { path } from 'minigame-std'; * * path.dirname('/usr/local/file.txt'); // '/usr/local' * path.dirname('/usr/local/'); // '/usr' * path.dirname('file.txt'); // '.' * path.dirname('/'); // '/' * ``` */ function dirname(path) { if (path.length === 0) return "."; let end = path.length; while (end > 1 && path.charCodeAt(end - 1) === CHAR_FORWARD_SLASH) end--; let lastSlash = -1; for (let i = end - 1; i >= 0; i--) if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { lastSlash = i; break; } if (lastSlash === -1) return "."; if (lastSlash === 0) return "/"; let dirEnd = lastSlash; while (dirEnd > 1 && path.charCodeAt(dirEnd - 1) === CHAR_FORWARD_SLASH) dirEnd--; return path.slice(0, dirEnd); } /** * 规范化路径,解析 '.' 和 '..' 片段,合并多余斜杠。 * @param path - 要规范化的路径字符串。 * @returns 规范化后的路径。 * @since 2.4.0 * @example * ```ts * import { path } from 'minigame-std'; * * path.normalize('/foo/bar//baz/asdf/quux/..'); // '/foo/bar/baz/asdf' * path.normalize('./foo/../bar/baz'); // 'bar/baz' * path.normalize('/foo/bar///baz'); // '/foo/bar/baz' * ``` */ function normalize(path) { if (path.length === 0) return "."; const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; path = normalizeString(path, !isAbsolute); if (path.length === 0 && !isAbsolute) path = "."; if (path.length > 0 && trailingSeparator) path += "/"; if (isAbsolute) return `/${path}`; return path; } /** * 解析路径中的 '.' 和 '..' 片段。 * 移植自 path-browserify。 */ function normalizeString(path, allowAboveRoot) { let res = ""; let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; let code; for (let i = 0; i <= path.length; i++) { if (i < path.length) code = path.charCodeAt(i); else code = CHAR_FORWARD_SLASH; if (code === CHAR_FORWARD_SLASH) { if (lastSlash === i - 1 || dots === 1) {} else if (dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== CHAR_DOT || res.charCodeAt(res.length - 2) !== CHAR_DOT) { if (res.length > 2) { const lastSlashIndex = res.lastIndexOf("/"); if (lastSlashIndex === -1) { res = ""; lastSegmentLength = 0; } else { res = res.slice(0, lastSlashIndex); lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); } lastSlash = i; dots = 0; continue; } else if (res.length === 2 || res.length === 1) { res = ""; lastSegmentLength = 0; lastSlash = i; dots = 0; continue; } } if (allowAboveRoot) { if (res.length > 0) res += `/..`; else res = ".."; lastSegmentLength = 2; } } else { if (res.length > 0) res += `/${path.slice(lastSlash + 1, i)}`; else res = path.slice(lastSlash + 1, i); lastSegmentLength = i - lastSlash - 1; } lastSlash = i; dots = 0; } else if (code === CHAR_DOT && dots !== -1) dots++; else dots = -1; } return res; } //#endregion //#region src/std/utils/resultify.ts /** * 将小游戏异步 API 转换为返回 `AsyncResult<T, E>` 的新函数,需要转换的 API 必须是接受可选 `success` 和 `fail` 回调的函数,并且其返回值必须是 `void` 或 `PromiseLike`。 * * 其中 `T` 为 `success` 回调的参数类型,`E` 为 `fail` 回调的参数类型。 * * @param api - 小游戏异步 API。 * @returns 返回一个新的函数,该函数返回 `AsyncResult<T, E>`。 * @since 2.0.0 * @example * ```ts * // 将 wx.setStorage 转换为 AsyncResult 风格 * const setStorageAsync = asyncResultify(wx.setStorage); * const result = await setStorageAsync({ key: 'test', data: 'value' }); * if (result.isOk()) { * console.log('存储成功'); * } else { * console.error('存储失败:', result.unwrapErr()); * } * ``` */ function asyncResultify(api) { return ((...args) => { const future = new tiny_future.Future(); const options = args[0] ?? {}; const { success, fail } = options; options.success = (res) => { success?.(res); future.resolve((0, happy_rusty.Ok)(res)); }; options.fail = (err) => { fail?.(err); future.resolve((0, happy_rusty.Err)(err)); }; const ret = api(options); if (ret != null && typeof ret === "object" && typeof ret.then === "function") return (0, happy_rusty.tryAsyncResult)(ret); else if (ret !== void 0) {} return future.promise; }); } /** * `asyncResultify` 的变体,将小游戏异步 API 转换为返回 `AsyncIOResult<T>` 的新函数。 * * 与 `asyncResultify` 不同的是,此函数会将 `fail` 回调的 `WechatMinigame.GeneralCallbackResult` 转换为 `Error` 类型。 * * @param api - 小游戏异步 API。 * @returns 返回一个新的函数,该函数返回 `AsyncIOResult<T>`。 * @since 2.0.0 * @example * ```ts * // 将 wx.setStorage 转换为 AsyncIOResult 风格 * const setStorageAsync = asyncIOResultify(wx.setStorage); * const result = await setStorageAsync({ key: 'test', data: 'value' }); * if (result.isOk()) { * console.log('存储成功'); * } else { * console.error('存储失败:', result.unwrapErr().message); * } * ``` */ function asyncIOResultify(api) { const wrapped = asyncResultify(api); return (async (...args) => { return (await wrapped(...args)).mapErr(miniGameFailureToError); }); } /** * 将小游戏同步 API 转换为返回 `IOResult<T>` 的新函数。 * * 功能类似于 `tryGeneralSyncOp`,但以函数包装的方式使用,将可能抛出的异常捕获并转换为 `IOResult`。 * * @param api - 小游戏同步 API。 * @returns 返回一个新的函数,该函数返回 `IOResult<T>`。 * @since 2.0.0 * @example * ```ts * // 将 wx.getStorageSync 转换为 IOResult 风格 * const getStorageSync = syncIOResultify(wx.getStorageSync); * const result = getStorageSync('test'); * if (result.isOk()) { * console.log('获取成功:', result.unwrap()); * } else { * console.error('获取失败:', result.unwrapErr().message); * } * ``` */ function syncIOResultify(api) { return (...args) => { try { return (0, happy_rusty.Ok)(api(...args)); } catch (e) { return (0, happy_rusty.Err)(miniGameFailureToError(e)); } }; } //#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 = 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)(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 = 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)(() => 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` 的结果。 */ function getExistsResult(statResult, options) { return statResult.map((stats) => { const { isDirectory = false, isFile = false } = options ?? {}; return !(isDirectory && stats.isFile() || isFile && stats.isDirectory()); }).orElse((err) => { return isNotFoundError(err) ? happy_rusty.RESULT_FALSE : statResult.asErr(); }); } /** * 根据 `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}`)); } //#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 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 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 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 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() ? asyncResultify(getFs().rmdir)({ dirPath: path, recursive: true }) : 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 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(dirname(filePath)); if (mkdirRes.isErr()) return mkdirRes; } const contentsRes = getWriteFileContents(contents); if (contentsRes.isErr()) return contentsRes.asErr(); const { data, encoding } = contentsRes.unwrap(); return (await 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(dirname(destPath))).andThenAsync(() => { return copyFile(srcPath, destPath); }); else { const srcEntryPath = srcPath + "/" + path; const destEntryPath = destPath + "/" + 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(); 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 + "/" + 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 = validateSafeUrl(fileUrl); if (fileUrlRes.isErr()) return createFailedFetchTask(fileUrlRes); if (typeof filePath === "string") { const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return 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(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 = validateSafeUrl(fileUrl); if (fileUrlRes.isErr()) return createFailedFetchTask(fileUrlRes); const filePathRes = validateAbsolutePath(filePath); if (filePathRes.isErr()) return createFailedFetchTask(filePathRes); filePath = filePathRes.unwrap(); const { headers, ...rest } = options ?? {}; let aborted = false; const future = new tiny_future.Future(); const task = wx.uploadFile({ name: 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 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 = 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 + "/"] = EMPTY_BYTES; const tasks = []; for (const { path, stats } of statRes.unwrap()) { if (!path) continue; const entryName = preserveRoot ? sourceName + "/" + path : path; if (stats.isFile()) tasks.push((async () => { return (await readFile$1(sourcePath + "/" + path)).map((data) => ({ entryName, data })); })()); else zippable[entryName + "/"] = 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({ [basename(tempFilePath)]: readFileRes.unwrap() }, zipFilePath); }); } /** * 将错误对象转换为 IOResult 类型。 */ function miniGameFailureToResult(error) { return (0, happy_rusty.Err)(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 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 () => { return { path, stats: await convertFileSystemHandleToStats(handle) }; })()); 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().unlinkSyn