feat(trpc): webdav client + mount service with secret encryption

This commit is contained in:
noelorin 2026-09-25 02:48:17 +08:00
parent 2f5000cc97
commit cdc40d61b1
9 changed files with 486 additions and 5 deletions

View File

@ -18,6 +18,7 @@
"scripts": { "scripts": {
"build": "vite build && tsc -p tsconfig.dts.json", "build": "vite build && tsc -p tsconfig.dts.json",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run",
"lint": "biome check .", "lint": "biome check .",
"dev": "tsx watch src/server.ts" "dev": "tsx watch src/server.ts"
}, },
@ -27,12 +28,14 @@
"@app/models": "workspace:*", "@app/models": "workspace:*",
"@app/types": "workspace:*", "@app/types": "workspace:*",
"@trpc/server": "^11.19.0", "@trpc/server": "^11.19.0",
"drizzle-orm": "^0.45.3" "drizzle-orm": "^0.45.3",
"webdav": "^5.11.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.15.29", "@types/node": "^22.15.29",
"tsx": "^4.19.4", "tsx": "^4.19.4",
"typescript": "^7.0.2", "typescript": "^7.0.2",
"vite": "^8.3.0" "vite": "^8.3.0",
"vitest": "^5.0.1"
} }
} }

View File

@ -27,3 +27,7 @@ export type {
RequestResetInput, RequestResetInput,
ResetPasswordInput, ResetPasswordInput,
} from "./services/auth.service.js"; } from "./services/auth.service.js";
// Media / WebDAV
export { mountService } from "./services/mount.service.js";
export type { MountPublic } from "./services/mount.service.js";

View File

@ -0,0 +1,141 @@
import { TRPCError } from "@trpc/server";
import { mountDao, type MountRow } from "@app/dao";
import type { MountCreateInput, MountListDirInput } from "@app/types";
import { decryptSecret, encryptSecret } from "./secret.js";
import { createWebdav, joinWebdavPath, listDirectory, type DirEntry } from "./webdav-client.js";
export type MountPublic = {
id: string;
name: string;
type: string;
baseUrl: string;
username: string | null;
rootPath: string;
enabled: boolean;
createdAt: Date | null;
updatedAt: Date | null;
};
function toPublic(row: MountRow): MountPublic {
return {
id: row.id,
name: row.name,
type: row.type,
baseUrl: row.baseUrl,
username: row.username,
rootPath: row.rootPath,
enabled: row.enabled,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
async function withClient<T>(
row: Pick<MountRow, "baseUrl" | "username" | "secretEnc">,
fn: (client: ReturnType<typeof createWebdav>) => Promise<T>,
): Promise<T> {
const password = row.secretEnc ? decryptSecret(row.secretEnc) : "";
const client = createWebdav({
baseUrl: row.baseUrl,
username: row.username ?? undefined,
password,
});
return fn(client);
}
export const mountService = {
async list(userId: string): Promise<{ mounts: MountPublic[] }> {
const rows = await mountDao.listByUser(userId);
return { mounts: rows.map(toPublic) };
},
async create(userId: string, input: MountCreateInput): Promise<{ mount: MountPublic }> {
try {
const row = await mountDao.create({
userId,
name: input.name,
type: input.type,
baseUrl: input.baseUrl,
username: input.username ?? null,
secretEnc: input.password ? encryptSecret(input.password) : "",
rootPath: input.rootPath,
enabled: input.enabled,
});
return { mount: toPublic(row) };
} catch {
throw new TRPCError({ code: "CONFLICT", message: "挂载名称已存在或创建失败" });
}
},
async update(
userId: string,
input: {
id: string;
name?: string | undefined;
baseUrl?: string | undefined;
username?: string | undefined;
password?: string | undefined;
rootPath?: string | undefined;
enabled?: boolean | undefined;
},
): Promise<{ mount: MountPublic }> {
const patch: Record<string, unknown> = {};
if (input.name !== undefined) patch["name"] = input.name;
if (input.baseUrl !== undefined) patch["baseUrl"] = input.baseUrl;
if (input.username !== undefined) patch["username"] = input.username;
if (input.password !== undefined) {
patch["secretEnc"] = input.password ? encryptSecret(input.password) : "";
}
if (input.rootPath !== undefined) patch["rootPath"] = input.rootPath;
if (input.enabled !== undefined) patch["enabled"] = input.enabled;
const row = await mountDao.update(input.id, userId, patch);
if (!row) throw new TRPCError({ code: "NOT_FOUND", message: "挂载不存在" });
return { mount: toPublic(row) };
},
async delete(userId: string, id: string): Promise<{ success: true }> {
await mountDao.delete(id, userId);
return { success: true };
},
async test(input: {
baseUrl: string;
username?: string | undefined;
password?: string | undefined;
rootPath: string;
}): Promise<{ ok: boolean; message: string }> {
try {
const client = createWebdav({
baseUrl: input.baseUrl,
username: input.username,
password: input.password,
});
await listDirectory(client, input.rootPath || "/");
return { ok: true, message: "连接成功" };
} catch (e) {
return { ok: false, message: e instanceof Error ? e.message : "连接失败" };
}
},
async listDir(
userId: string,
input: MountListDirInput,
): Promise<{ entries: DirEntry[]; path: string }> {
const mount = await mountDao.getByIdForUser(input.mountId, userId);
if (!mount) throw new TRPCError({ code: "NOT_FOUND", message: "挂载不存在" });
const abs = joinWebdavPath(mount.rootPath, input.path);
try {
const entries = await withClient(mount, (c) => listDirectory(c, abs));
entries.sort((a, b) => {
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
return a.basename.localeCompare(b.basename);
});
return { entries, path: input.path };
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: e instanceof Error ? `读取目录失败: ${e.message}` : "读取目录失败",
});
}
},
};

View File

@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { decryptSecret, encryptSecret } from "./secret.js";
describe("secret", () => {
it("roundtrip", () => {
process.env["MEDIA_SECRET_KEY"] = "0".repeat(64);
const enc = encryptSecret("p@ss");
expect(enc).not.toContain("p@ss");
expect(decryptSecret(enc)).toBe("p@ss");
});
it("missing key throws", () => {
delete process.env["MEDIA_SECRET_KEY"];
expect(() => encryptSecret("x")).toThrow();
});
});

View File

@ -0,0 +1,28 @@
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
function key(): Buffer {
const hex = process.env["MEDIA_SECRET_KEY"];
if (hex?.length !== 64) {
throw new Error("MEDIA_SECRET_KEY 必须是 64 位 hex(openssl rand -hex 32)");
}
return Buffer.from(hex, "hex");
}
/** AES-256-GCM → base64(iv || tag || ciphertext) */
export function encryptSecret(plain: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key(), iv);
const enc = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, enc]).toString("base64");
}
export function decryptSecret(payload: string): string {
const buf = Buffer.from(payload, "base64");
const iv = buf.subarray(0, 12);
const tag = buf.subarray(12, 28);
const data = buf.subarray(28);
const decipher = createDecipheriv("aes-256-gcm", key(), iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
}

View File

@ -0,0 +1,13 @@
import { describe, expect, it } from "vitest";
import { joinWebdavPath, isVideoFilename } from "./webdav-client.js";
describe("webdav helpers", () => {
it("joinWebdavPath", () => {
expect(joinWebdavPath("/root", "a/b.mkv")).toBe("/root/a/b.mkv");
expect(joinWebdavPath("/", "x.mp4")).toBe("/x.mp4");
});
it("isVideoFilename", () => {
expect(isVideoFilename("E01.mkv")).toBe(true);
expect(isVideoFilename("readme.txt")).toBe(false);
});
});

View File

@ -0,0 +1,57 @@
import { createClient, type WebDAVClient } from "webdav";
const VIDEO_EXT = new Set([
".mp4",
".mkv",
".webm",
".avi",
".mov",
".m4v",
".ts",
".flv",
".wmv",
".mpg",
".mpeg",
]);
export function isVideoFilename(name: string): boolean {
const i = name.lastIndexOf(".");
if (i < 0) return false;
return VIDEO_EXT.has(name.slice(i).toLowerCase());
}
export function joinWebdavPath(root: string, rel: string): string {
const r = root.replace(/\/+$/, "") || "";
const p = rel.replace(/^\/+/, "");
if (!r || r === "/") return `/${p}`;
return `${r}/${p}`;
}
export function createWebdav(opts: {
baseUrl: string;
username?: string | undefined;
password?: string | undefined;
}): WebDAVClient {
return createClient(opts.baseUrl, {
username: opts.username ?? "",
password: opts.password ?? "",
httpAgent: undefined,
});
}
export type DirEntry = {
filename: string;
basename: string;
type: "directory" | "file";
size: number;
};
export async function listDirectory(client: WebDAVClient, absPath: string): Promise<DirEntry[]> {
const items = await client.getDirectoryContents(absPath);
return items.map((item) => ({
filename: item.filename,
basename: item.basename,
type: item.type,
size: Number(item.size),
}));
}

View File

@ -14,5 +14,6 @@
"esModuleInterop": true, "esModuleInterop": true,
"types": ["node"] "types": ["node"]
}, },
"include": ["src"] "include": ["src"],
"exclude": ["src/**/*.test.ts"]
} }

223
yarn.lock
View File

@ -106,6 +106,8 @@ __metadata:
tsx: "npm:^4.19.4" tsx: "npm:^4.19.4"
typescript: "npm:^7.0.2" typescript: "npm:^7.0.2"
vite: "npm:^8.3.0" vite: "npm:^8.3.0"
vitest: "npm:^5.0.1"
webdav: "npm:^5.11.0"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@ -813,6 +815,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@buttercup/fetch@npm:^0.2.1":
version: 0.2.1
resolution: "@buttercup/fetch@npm:0.2.1"
dependencies:
node-fetch: "npm:^3.3.0"
dependenciesMeta:
node-fetch:
optional: true
checksum: 10c0/e18cc830d7700d592812d9321d69d52537ae451eb3881021ed0a544d523e0a827ab1b8746823faa1166e17fd3257cbaa63ae9cc304ed534984e615c6c21f3944
languageName: node
linkType: hard
"@capsizecss/unpack@npm:^4.0.0": "@capsizecss/unpack@npm:^4.0.0":
version: 4.0.1 version: 4.0.1
resolution: "@capsizecss/unpack@npm:4.0.1" resolution: "@capsizecss/unpack@npm:4.0.1"
@ -2380,6 +2394,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@nodable/entities@npm:^3.0.0":
version: 3.0.0
resolution: "@nodable/entities@npm:3.0.0"
checksum: 10c0/5e57422ce08d9f83a7ad09d8f90953e2e63b3274c3f941bf7ddf64f290473e70d47acbd6c8b9e60169c2a8c5c2424c4131bdd99b937792413f55a4a146f76658
languageName: node
linkType: hard
"@oslojs/encoding@npm:^1.1.0": "@oslojs/encoding@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "@oslojs/encoding@npm:1.1.0" resolution: "@oslojs/encoding@npm:1.1.0"
@ -4432,6 +4453,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"anynum@npm:^1.0.1":
version: 1.0.1
resolution: "anynum@npm:1.0.1"
checksum: 10c0/cfda92c9215722fc4b15faa33d0eb3d5dfd28fba6cb67c1f50d214feaa7ba6497814a3e110777853585de6d91c8c962a1ae425b637d3598d9f9d8f6f6802e5bb
languageName: node
linkType: hard
"app-template@workspace:.": "app-template@workspace:.":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "app-template@workspace:." resolution: "app-template@workspace:."
@ -4606,6 +4634,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"base-64@npm:^1.0.0":
version: 1.0.0
resolution: "base-64@npm:1.0.0"
checksum: 10c0/d886cb3236cee0bed9f7075675748b59b32fad623ddb8ce1793c790306aa0f76a03238cad4b3fb398abda6527ce08a5588388533a4ccade0b97e82b9da660e28
languageName: node
linkType: hard
"baseline-browser-mapping@npm:^2.11.23": "baseline-browser-mapping@npm:^2.11.23":
version: 2.11.25 version: 2.11.25
resolution: "baseline-browser-mapping@npm:2.11.25" resolution: "baseline-browser-mapping@npm:2.11.25"
@ -4662,6 +4697,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"byte-length@npm:^1.0.2":
version: 1.0.2
resolution: "byte-length@npm:1.0.2"
checksum: 10c0/98778b938318494c2eadedf83b415e63da406d905575101eb102b7eefb5fafbbe21fbe83001914283664bb2fb93bd46f99245af2e8420a928ba90ffbb58041a1
languageName: node
linkType: hard
"callsites@npm:^3.0.0": "callsites@npm:^3.0.0":
version: 3.1.0 version: 3.1.0
resolution: "callsites@npm:3.1.0" resolution: "callsites@npm:3.1.0"
@ -4724,6 +4766,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"charenc@npm:0.0.2":
version: 0.0.2
resolution: "charenc@npm:0.0.2"
checksum: 10c0/a45ec39363a16799d0f9365c8dd0c78e711415113c6f14787a22462ef451f5013efae8a28f1c058f81fc01f2a6a16955f7a5fd0cd56247ce94a45349c89877d8
languageName: node
linkType: hard
"check-error@npm:^2.1.1": "check-error@npm:^2.1.1":
version: 2.1.3 version: 2.1.3
resolution: "check-error@npm:2.1.3" resolution: "check-error@npm:2.1.3"
@ -4928,6 +4977,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"crypt@npm:0.0.2":
version: 0.0.2
resolution: "crypt@npm:0.0.2"
checksum: 10c0/adbf263441dd801665d5425f044647533f39f4612544071b1471962209d235042fb703c27eea2795c7c53e1dfc242405173003f83cf4f4761a633d11f9653f18
languageName: node
linkType: hard
"css-select@npm:^6.0.0": "css-select@npm:^6.0.0":
version: 6.0.0 version: 6.0.0
resolution: "css-select@npm:6.0.0" resolution: "css-select@npm:6.0.0"
@ -5355,6 +5411,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"entities@npm:^6.0.1":
version: 6.0.1
resolution: "entities@npm:6.0.1"
checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414
languageName: node
linkType: hard
"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1":
version: 2.2.1 version: 2.2.1
resolution: "env-paths@npm:2.2.1" resolution: "env-paths@npm:2.2.1"
@ -5873,6 +5936,32 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-xml-builder@npm:^1.2.0":
version: 1.3.1
resolution: "fast-xml-builder@npm:1.3.1"
dependencies:
path-expression-matcher: "npm:^1.6.2"
xml-naming: "npm:^0.3.0"
checksum: 10c0/5aaf30465e6ba4ae9780eb3916878d5bd40763a3fd6b8b079a76aabe8f3e9e852327280e7c1be2b63ec141424b6ec61b7ab8acca8b3948779b164b004a294b4c
languageName: node
linkType: hard
"fast-xml-parser@npm:^5.11.1":
version: 5.11.1
resolution: "fast-xml-parser@npm:5.11.1"
dependencies:
"@nodable/entities": "npm:^3.0.0"
fast-xml-builder: "npm:^1.2.0"
is-unsafe: "npm:^2.0.0"
path-expression-matcher: "npm:^1.6.2"
strnum: "npm:^2.4.2"
xml-naming: "npm:^0.3.0"
bin:
fxparser: src/cli/cli.js
checksum: 10c0/04a1cf600130c4fc146e9f5bcb0a0e88354c5b9725debfa849f9028afd90b77a3e4c9339a3a7f5d22d749b74ac78567428eb1385679319fabf4d6f03065248d6
languageName: node
linkType: hard
"fdir@npm:^6.4.4, fdir@npm:^6.5.0": "fdir@npm:^6.4.4, fdir@npm:^6.5.0":
version: 6.5.0 version: 6.5.0
resolution: "fdir@npm:6.5.0" resolution: "fdir@npm:6.5.0"
@ -6159,6 +6248,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"hot-patcher@npm:^2.0.1":
version: 2.0.1
resolution: "hot-patcher@npm:2.0.1"
checksum: 10c0/cabccf0d78b9604cdd73b88d1cfcdff5ae27b501df97ee20131979513cc17c532f4f04605d24ac9225d2c046fc435062cec86b49f9fb3f524b446a296e8c1b62
languageName: node
linkType: hard
"html-escaper@npm:3.0.3": "html-escaper@npm:3.0.3":
version: 3.0.3 version: 3.0.3
resolution: "html-escaper@npm:3.0.3" resolution: "html-escaper@npm:3.0.3"
@ -6238,6 +6334,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-buffer@npm:~1.1.6":
version: 1.1.6
resolution: "is-buffer@npm:1.1.6"
checksum: 10c0/ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234
languageName: node
linkType: hard
"is-core-module@npm:^2.16.1": "is-core-module@npm:^2.16.1":
version: 2.17.0 version: 2.17.0
resolution: "is-core-module@npm:2.17.0" resolution: "is-core-module@npm:2.17.0"
@ -6279,6 +6382,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-unsafe@npm:^2.0.0":
version: 2.0.2
resolution: "is-unsafe@npm:2.0.2"
checksum: 10c0/3f92f6534e326ae13f4a4733d0d6b9f06f2b7ab80d99c1973eb32b33b00cf9bc7a8505e3260f36209c9109f56a46bb64cc6bd0a27e37fee08cd35892a2b6ae44
languageName: node
linkType: hard
"is-wsl@npm:^2.2.0": "is-wsl@npm:^2.2.0":
version: 2.2.0 version: 2.2.0
resolution: "is-wsl@npm:2.2.0" resolution: "is-wsl@npm:2.2.0"
@ -6418,6 +6528,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"layerr@npm:^3.0.0":
version: 3.0.0
resolution: "layerr@npm:3.0.0"
checksum: 10c0/320c9b9cf1392c73c9ff8f8d1bb7a782093ac7341fe5d7fea6ebfeea6d785af8e0fc573541431b6ffcb268577f8aea66168757e73a15035568a00ab9e4370705
languageName: node
linkType: hard
"lefthook-darwin-arm64@npm:2.1.14": "lefthook-darwin-arm64@npm:2.1.14":
version: 2.1.14 version: 2.1.14
resolution: "lefthook-darwin-arm64@npm:2.1.14" resolution: "lefthook-darwin-arm64@npm:2.1.14"
@ -6911,6 +7028,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"md5@npm:^2.3.0":
version: 2.3.0
resolution: "md5@npm:2.3.0"
dependencies:
charenc: "npm:0.0.2"
crypt: "npm:0.0.2"
is-buffer: "npm:~1.1.6"
checksum: 10c0/14a21d597d92e5b738255fbe7fe379905b8cb97e0a49d44a20b58526a646ec5518c337b817ce0094ca94d3e81a3313879c4c7b510d250c282d53afbbdede9110
languageName: node
linkType: hard
"mdast-util-phrasing@npm:^4.0.0": "mdast-util-phrasing@npm:^4.0.0":
version: 4.1.0 version: 4.1.0
resolution: "mdast-util-phrasing@npm:4.1.0" resolution: "mdast-util-phrasing@npm:4.1.0"
@ -7075,7 +7203,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"minimatch@npm:^9.0.4": "minimatch@npm:^9.0.4, minimatch@npm:^9.0.9":
version: 9.0.9 version: 9.0.9
resolution: "minimatch@npm:9.0.9" resolution: "minimatch@npm:9.0.9"
dependencies: dependencies:
@ -7144,6 +7272,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"nested-property@npm:^4.0.0":
version: 4.0.0
resolution: "nested-property@npm:4.0.0"
checksum: 10c0/7bc0514f3d10460cc07ea27a39ce75f81471a28b8b019d4bfd9eda41dcd92c1fcb291598d7e168ae8bf1324109b36325e3a563d6aa2537d13015bea9258b8b72
languageName: node
linkType: hard
"nlcst-to-string@npm:^4.0.0": "nlcst-to-string@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "nlcst-to-string@npm:4.0.0" resolution: "nlcst-to-string@npm:4.0.0"
@ -7167,7 +7302,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-fetch@npm:^3.3.2": "node-fetch@npm:^3.3.0, node-fetch@npm:^3.3.2":
version: 3.3.2 version: 3.3.2
resolution: "node-fetch@npm:3.3.2" resolution: "node-fetch@npm:3.3.2"
dependencies: dependencies:
@ -7395,6 +7530,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"path-expression-matcher@npm:^1.6.2":
version: 1.6.2
resolution: "path-expression-matcher@npm:1.6.2"
checksum: 10c0/8241302f563de367a81acacd417055a22dd3792a84700569dc2a7888da31aa18eb01131c55f05200c92790b6c45b01fee1984b02540cc5f15726ba1b73a8a075
languageName: node
linkType: hard
"path-key@npm:^3.1.0": "path-key@npm:^3.1.0":
version: 3.1.1 version: 3.1.1
resolution: "path-key@npm:3.1.1" resolution: "path-key@npm:3.1.1"
@ -7409,6 +7551,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"path-posix@npm:^1.0.0":
version: 1.0.0
resolution: "path-posix@npm:1.0.0"
checksum: 10c0/00fbadb9b60fb513f316f92e0b5535e55d832f4f20067586d151f6d7bed57178dec31b1a0f514694500a9a1f2b69798c066a3cdcf0b0289cfee63e39845bfd02
languageName: node
linkType: hard
"path-scurry@npm:^1.11.1": "path-scurry@npm:^1.11.1":
version: 1.11.1 version: 1.11.1
resolution: "path-scurry@npm:1.11.1" resolution: "path-scurry@npm:1.11.1"
@ -7509,6 +7658,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"querystringify@npm:^2.1.1":
version: 2.2.0
resolution: "querystringify@npm:2.2.0"
checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa
languageName: node
linkType: hard
"radix3@npm:^1.1.2": "radix3@npm:^1.1.2":
version: 1.1.2 version: 1.1.2
resolution: "radix3@npm:1.1.2" resolution: "radix3@npm:1.1.2"
@ -7709,6 +7865,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"requires-port@npm:^1.0.0":
version: 1.0.0
resolution: "requires-port@npm:1.0.0"
checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267
languageName: node
linkType: hard
"resolve-from@npm:^4.0.0": "resolve-from@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "resolve-from@npm:4.0.0" resolution: "resolve-from@npm:4.0.0"
@ -8408,6 +8571,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"strnum@npm:^2.4.2":
version: 2.4.2
resolution: "strnum@npm:2.4.2"
dependencies:
anynum: "npm:^1.0.1"
checksum: 10c0/71a94dcc12cf3187e10089ed2ddcf7c289fed168ce4a33ee393bbd009a9199c237e3ef71597f3c91fac4203dd1e675c090eb4131226fd70af6fa2a31b0175de2
languageName: node
linkType: hard
"supports-preserve-symlinks-flag@npm:^1.0.0": "supports-preserve-symlinks-flag@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "supports-preserve-symlinks-flag@npm:1.0.0" resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
@ -9067,6 +9239,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"url-join@npm:^5.0.0":
version: 5.0.0
resolution: "url-join@npm:5.0.0"
checksum: 10c0/ed2b166b4b5a98adcf6828a48b6bd6df1dac4c8a464a73cf4d8e2457ed410dd8da6be0d24855b86026cd7f5c5a3657c1b7b2c7a7c5b8870af17635a41387b04c
languageName: node
linkType: hard
"url-parse@npm:^1.5.10":
version: 1.5.10
resolution: "url-parse@npm:1.5.10"
dependencies:
querystringify: "npm:^2.1.1"
requires-port: "npm:^1.0.0"
checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87
languageName: node
linkType: hard
"use-callback-ref@npm:^1.3.3": "use-callback-ref@npm:^1.3.3":
version: 1.3.3 version: 1.3.3
resolution: "use-callback-ref@npm:1.3.3" resolution: "use-callback-ref@npm:1.3.3"
@ -9603,6 +9792,29 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"webdav@npm:^5.11.0":
version: 5.11.0
resolution: "webdav@npm:5.11.0"
dependencies:
"@buttercup/fetch": "npm:^0.2.1"
"@nodable/entities": "npm:^3.0.0"
base-64: "npm:^1.0.0"
byte-length: "npm:^1.0.2"
entities: "npm:^6.0.1"
fast-xml-parser: "npm:^5.11.1"
hot-patcher: "npm:^2.0.1"
layerr: "npm:^3.0.0"
md5: "npm:^2.3.0"
minimatch: "npm:^9.0.9"
nested-property: "npm:^4.0.0"
node-fetch: "npm:^3.3.2"
path-posix: "npm:^1.0.0"
url-join: "npm:^5.0.0"
url-parse: "npm:^1.5.10"
checksum: 10c0/e980ee6b34ec0f0a72023c91f699f528d07d486241c286e6a691b019ef3954474c00625f1b4acd1e7203e1329d412582db7c41b4c11f9c1b7628e4aaaee585b8
languageName: node
linkType: hard
"webpack-virtual-modules@npm:^0.6.2": "webpack-virtual-modules@npm:^0.6.2":
version: 0.6.2 version: 0.6.2
resolution: "webpack-virtual-modules@npm:0.6.2" resolution: "webpack-virtual-modules@npm:0.6.2"
@ -9703,6 +9915,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"xml-naming@npm:^0.3.0":
version: 0.3.0
resolution: "xml-naming@npm:0.3.0"
checksum: 10c0/48bfc4fe888cdf1c3eceb7853632ebce59e4aa71f0ae33c3f9ce1fbd3213caad293457de0a311114491f0d4efcfba70977dcba3a4f66dfed8c92edbb938056bf
languageName: node
linkType: hard
"xxhash-wasm@npm:^1.1.0": "xxhash-wasm@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "xxhash-wasm@npm:1.1.0" resolution: "xxhash-wasm@npm:1.1.0"