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