diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 7865b0a..8c2b16f 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -18,6 +18,7 @@ "scripts": { "build": "vite build && tsc -p tsconfig.dts.json", "typecheck": "tsc --noEmit", + "test": "vitest run", "lint": "biome check .", "dev": "tsx watch src/server.ts" }, @@ -27,12 +28,14 @@ "@app/models": "workspace:*", "@app/types": "workspace:*", "@trpc/server": "^11.19.0", - "drizzle-orm": "^0.45.3" + "drizzle-orm": "^0.45.3", + "webdav": "^5.11.0" }, "devDependencies": { "@types/node": "^22.15.29", "tsx": "^4.19.4", "typescript": "^7.0.2", - "vite": "^8.3.0" + "vite": "^8.3.0", + "vitest": "^5.0.1" } } diff --git a/packages/trpc/src/index.ts b/packages/trpc/src/index.ts index 1b60e88..83682eb 100644 --- a/packages/trpc/src/index.ts +++ b/packages/trpc/src/index.ts @@ -27,3 +27,7 @@ export type { RequestResetInput, ResetPasswordInput, } from "./services/auth.service.js"; + +// Media / WebDAV +export { mountService } from "./services/mount.service.js"; +export type { MountPublic } from "./services/mount.service.js"; diff --git a/packages/trpc/src/services/mount.service.ts b/packages/trpc/src/services/mount.service.ts new file mode 100644 index 0000000..7f6ebc1 --- /dev/null +++ b/packages/trpc/src/services/mount.service.ts @@ -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( + row: Pick, + fn: (client: ReturnType) => Promise, +): Promise { + 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 = {}; + 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}` : "读取目录失败", + }); + } + }, +}; diff --git a/packages/trpc/src/services/secret.test.ts b/packages/trpc/src/services/secret.test.ts new file mode 100644 index 0000000..a85c8af --- /dev/null +++ b/packages/trpc/src/services/secret.test.ts @@ -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(); + }); +}); diff --git a/packages/trpc/src/services/secret.ts b/packages/trpc/src/services/secret.ts new file mode 100644 index 0000000..477d1dc --- /dev/null +++ b/packages/trpc/src/services/secret.ts @@ -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"); +} diff --git a/packages/trpc/src/services/webdav-client.test.ts b/packages/trpc/src/services/webdav-client.test.ts new file mode 100644 index 0000000..193f30e --- /dev/null +++ b/packages/trpc/src/services/webdav-client.test.ts @@ -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); + }); +}); diff --git a/packages/trpc/src/services/webdav-client.ts b/packages/trpc/src/services/webdav-client.ts new file mode 100644 index 0000000..f088410 --- /dev/null +++ b/packages/trpc/src/services/webdav-client.ts @@ -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 { + const items = await client.getDirectoryContents(absPath); + return items.map((item) => ({ + filename: item.filename, + basename: item.basename, + type: item.type, + size: Number(item.size), + })); +} diff --git a/packages/trpc/tsconfig.dts.json b/packages/trpc/tsconfig.dts.json index 2ba2bc0..cbb3164 100644 --- a/packages/trpc/tsconfig.dts.json +++ b/packages/trpc/tsconfig.dts.json @@ -14,5 +14,6 @@ "esModuleInterop": true, "types": ["node"] }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts"] } diff --git a/yarn.lock b/yarn.lock index 48870c1..247e93f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -106,6 +106,8 @@ __metadata: tsx: "npm:^4.19.4" typescript: "npm:^7.0.2" vite: "npm:^8.3.0" + vitest: "npm:^5.0.1" + webdav: "npm:^5.11.0" languageName: unknown linkType: soft @@ -813,6 +815,18 @@ __metadata: languageName: node 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": version: 4.0.1 resolution: "@capsizecss/unpack@npm:4.0.1" @@ -2380,6 +2394,13 @@ __metadata: languageName: node 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": version: 1.1.0 resolution: "@oslojs/encoding@npm:1.1.0" @@ -4432,6 +4453,13 @@ __metadata: languageName: node 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:.": version: 0.0.0-use.local resolution: "app-template@workspace:." @@ -4606,6 +4634,13 @@ __metadata: languageName: node 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": version: 2.11.25 resolution: "baseline-browser-mapping@npm:2.11.25" @@ -4662,6 +4697,13 @@ __metadata: languageName: node 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": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -4724,6 +4766,13 @@ __metadata: languageName: node 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": version: 2.1.3 resolution: "check-error@npm:2.1.3" @@ -4928,6 +4977,13 @@ __metadata: languageName: node 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": version: 6.0.0 resolution: "css-select@npm:6.0.0" @@ -5355,6 +5411,13 @@ __metadata: languageName: node 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": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -5873,6 +5936,32 @@ __metadata: languageName: node 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": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -6159,6 +6248,13 @@ __metadata: languageName: node 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": version: 3.0.3 resolution: "html-escaper@npm:3.0.3" @@ -6238,6 +6334,13 @@ __metadata: languageName: node 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": version: 2.17.0 resolution: "is-core-module@npm:2.17.0" @@ -6279,6 +6382,13 @@ __metadata: languageName: node 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": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -6418,6 +6528,13 @@ __metadata: languageName: node 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": version: 2.1.14 resolution: "lefthook-darwin-arm64@npm:2.1.14" @@ -6911,6 +7028,17 @@ __metadata: languageName: node 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": version: 4.1.0 resolution: "mdast-util-phrasing@npm:4.1.0" @@ -7075,7 +7203,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.9": version: 9.0.9 resolution: "minimatch@npm:9.0.9" dependencies: @@ -7144,6 +7272,13 @@ __metadata: languageName: node 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": version: 4.0.0 resolution: "nlcst-to-string@npm:4.0.0" @@ -7167,7 +7302,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^3.3.2": +"node-fetch@npm:^3.3.0, node-fetch@npm:^3.3.2": version: 3.3.2 resolution: "node-fetch@npm:3.3.2" dependencies: @@ -7395,6 +7530,13 @@ __metadata: languageName: node 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": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -7409,6 +7551,13 @@ __metadata: languageName: node 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": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" @@ -7509,6 +7658,13 @@ __metadata: languageName: node 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": version: 1.1.2 resolution: "radix3@npm:1.1.2" @@ -7709,6 +7865,13 @@ __metadata: languageName: node 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": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -8408,6 +8571,15 @@ __metadata: languageName: node 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": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -9067,6 +9239,23 @@ __metadata: languageName: node 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": version: 1.3.3 resolution: "use-callback-ref@npm:1.3.3" @@ -9603,6 +9792,29 @@ __metadata: languageName: node 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": version: 0.6.2 resolution: "webpack-virtual-modules@npm:0.6.2" @@ -9703,6 +9915,13 @@ __metadata: languageName: node 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": version: 1.1.0 resolution: "xxhash-wasm@npm:1.1.0"