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
JavaScript
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