@nextclaw/server 0.15.23 → 0.15.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +35 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +337 -33
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { serve } from "@hono/node-server";
|
|
|
4
4
|
import { AccessManager, PanelAppError, injectUiContentParamsBootstrap, isInboxDeliveryError, isPanelAppError, isPreferenceError, isProjectError, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError } from "@nextclaw/kernel";
|
|
5
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { mkdir, open, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
7
|
+
import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
9
9
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
10
10
|
import * as NextclawCore from "@nextclaw/core";
|
|
@@ -4839,6 +4839,8 @@ var ServiceAppsRoutesController = class {
|
|
|
4839
4839
|
};
|
|
4840
4840
|
//#endregion
|
|
4841
4841
|
//#region src/app/utils/ncp-session-event-stream.utils.ts
|
|
4842
|
+
const NCP_SESSION_STREAM_HEARTBEAT_INTERVAL_MS = 25e3;
|
|
4843
|
+
const NCP_SESSION_STREAM_HEARTBEAT_FRAME = ": keepalive\n\n";
|
|
4842
4844
|
function readEventSessionId(event) {
|
|
4843
4845
|
const payload = "payload" in event ? event.payload : null;
|
|
4844
4846
|
if (!payload || typeof payload !== "object") return null;
|
|
@@ -4852,8 +4854,13 @@ function createNcpSessionEventStreamResponse(eventBus, payload, signal) {
|
|
|
4852
4854
|
let controller = null;
|
|
4853
4855
|
let closed = false;
|
|
4854
4856
|
let unsubscribe = () => void 0;
|
|
4857
|
+
let heartbeat = null;
|
|
4855
4858
|
const cleanup = () => {
|
|
4856
4859
|
unsubscribe();
|
|
4860
|
+
if (heartbeat) {
|
|
4861
|
+
clearInterval(heartbeat);
|
|
4862
|
+
heartbeat = null;
|
|
4863
|
+
}
|
|
4857
4864
|
signal.removeEventListener("abort", close);
|
|
4858
4865
|
};
|
|
4859
4866
|
const close = () => {
|
|
@@ -4870,6 +4877,9 @@ function createNcpSessionEventStreamResponse(eventBus, payload, signal) {
|
|
|
4870
4877
|
start: (streamController) => {
|
|
4871
4878
|
controller = streamController;
|
|
4872
4879
|
unsubscribe = eventBus.on(eventKeys.ncpEvent, push);
|
|
4880
|
+
heartbeat = setInterval(() => {
|
|
4881
|
+
if (!closed && !signal.aborted) controller?.enqueue(encoder.encode(NCP_SESSION_STREAM_HEARTBEAT_FRAME));
|
|
4882
|
+
}, NCP_SESSION_STREAM_HEARTBEAT_INTERVAL_MS);
|
|
4873
4883
|
signal.addEventListener("abort", close, { once: true });
|
|
4874
4884
|
if (signal.aborted) close();
|
|
4875
4885
|
},
|
|
@@ -5079,6 +5089,28 @@ function isServerPathBrowseError(error) {
|
|
|
5079
5089
|
return error instanceof ServerPathBrowseError;
|
|
5080
5090
|
}
|
|
5081
5091
|
//#endregion
|
|
5092
|
+
//#region src/features/server-path/utils/server-path-search.utils.ts
|
|
5093
|
+
function isServerPathInside(basePath, candidatePath) {
|
|
5094
|
+
const relativePath = relative(basePath, candidatePath);
|
|
5095
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
5096
|
+
}
|
|
5097
|
+
function normalizeServerPathRelativePath(value) {
|
|
5098
|
+
return value.split(sep).join("/");
|
|
5099
|
+
}
|
|
5100
|
+
function resolveServerPathSearchScore(entry, query) {
|
|
5101
|
+
if (!query) return 1;
|
|
5102
|
+
const normalizedQuery = query.toLocaleLowerCase();
|
|
5103
|
+
const normalizedName = entry.name.toLocaleLowerCase();
|
|
5104
|
+
const normalizedPath = entry.relativePath.toLocaleLowerCase();
|
|
5105
|
+
if (!normalizedQuery.split(/\s+/).filter(Boolean).every((term) => normalizedPath.includes(term))) return 0;
|
|
5106
|
+
if (normalizedName === normalizedQuery) return 1e3;
|
|
5107
|
+
if (normalizedName.startsWith(normalizedQuery)) return 850;
|
|
5108
|
+
if (normalizedName.includes(normalizedQuery)) return 700;
|
|
5109
|
+
if (normalizedPath.startsWith(normalizedQuery)) return 600;
|
|
5110
|
+
if (normalizedPath.split("/").some((segment) => segment.startsWith(normalizedQuery))) return 550;
|
|
5111
|
+
return 400 - Math.min(normalizedPath.length, 300);
|
|
5112
|
+
}
|
|
5113
|
+
//#endregion
|
|
5082
5114
|
//#region src/features/server-path/utils/server-path-directory.utils.ts
|
|
5083
5115
|
var ServerPathDirectoryCreateError = class extends Error {
|
|
5084
5116
|
constructor(code, message) {
|
|
@@ -5092,28 +5124,46 @@ function normalizeDirectoryName(value) {
|
|
|
5092
5124
|
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\")) throw new ServerPathDirectoryCreateError("SERVER_PATH_DIRECTORY_NAME_INVALID", "directory name is invalid");
|
|
5093
5125
|
return name;
|
|
5094
5126
|
}
|
|
5095
|
-
function readFileErrorCode(error) {
|
|
5127
|
+
function readFileErrorCode$1(error) {
|
|
5096
5128
|
return typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : void 0;
|
|
5097
5129
|
}
|
|
5098
|
-
|
|
5099
|
-
let parentPath;
|
|
5130
|
+
function resolveDirectoryPath(value) {
|
|
5100
5131
|
try {
|
|
5101
|
-
|
|
5132
|
+
return resolveServerPath({ path: typeof value === "string" ? value : null });
|
|
5102
5133
|
} catch (error) {
|
|
5103
5134
|
if (error instanceof ServerPathResolutionError) throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_INVALID", error.message);
|
|
5104
5135
|
throw error;
|
|
5105
5136
|
}
|
|
5137
|
+
}
|
|
5138
|
+
async function assertParentInsideBase(baseValue, parentPath) {
|
|
5139
|
+
if (typeof baseValue !== "string" || !baseValue.trim()) return;
|
|
5140
|
+
const basePath = resolveDirectoryPath(baseValue);
|
|
5141
|
+
if (!isServerPathInside(basePath, parentPath)) throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_INVALID", "parent path must stay inside the project root");
|
|
5142
|
+
try {
|
|
5143
|
+
const [realBasePath, realParentPath] = await Promise.all([realpath(basePath), realpath(parentPath)]);
|
|
5144
|
+
if (!isServerPathInside(realBasePath, realParentPath)) throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_INVALID", "parent path must stay inside the project root");
|
|
5145
|
+
} catch (error) {
|
|
5146
|
+
if (error instanceof ServerPathDirectoryCreateError) throw error;
|
|
5147
|
+
throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_INVALID", "project root and parent path must exist");
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
function throwDirectoryCreationError(error) {
|
|
5151
|
+
const code = readFileErrorCode$1(error);
|
|
5152
|
+
if (code === "EEXIST") throw new ServerPathDirectoryCreateError("SERVER_PATH_DIRECTORY_EXISTS", "directory already exists");
|
|
5153
|
+
if (code === "ENOENT") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_FOUND", "parent directory does not exist");
|
|
5154
|
+
if (code === "ENOTDIR") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_DIRECTORY", "parent path must point to a directory");
|
|
5155
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_WRITABLE", "parent directory is not writable");
|
|
5156
|
+
throw error;
|
|
5157
|
+
}
|
|
5158
|
+
async function createServerPathDirectory(input) {
|
|
5159
|
+
const parentPath = resolveDirectoryPath(input.parentPath);
|
|
5160
|
+
await assertParentInsideBase(input.basePath, parentPath);
|
|
5106
5161
|
const directoryPath = join(parentPath, normalizeDirectoryName(input.name));
|
|
5107
5162
|
try {
|
|
5108
5163
|
await mkdir(directoryPath);
|
|
5109
5164
|
return { path: directoryPath };
|
|
5110
5165
|
} catch (error) {
|
|
5111
|
-
|
|
5112
|
-
if (code === "EEXIST") throw new ServerPathDirectoryCreateError("SERVER_PATH_DIRECTORY_EXISTS", "directory already exists");
|
|
5113
|
-
if (code === "ENOENT") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_FOUND", "parent directory does not exist");
|
|
5114
|
-
if (code === "ENOTDIR") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_DIRECTORY", "parent path must point to a directory");
|
|
5115
|
-
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathDirectoryCreateError("SERVER_PATH_PARENT_NOT_WRITABLE", "parent directory is not writable");
|
|
5116
|
-
throw error;
|
|
5166
|
+
throwDirectoryCreationError(error);
|
|
5117
5167
|
}
|
|
5118
5168
|
}
|
|
5119
5169
|
function isServerPathDirectoryCreateError(error) {
|
|
@@ -5405,28 +5455,6 @@ function isServerPathContentError(error) {
|
|
|
5405
5455
|
return error instanceof ServerPathContentError;
|
|
5406
5456
|
}
|
|
5407
5457
|
//#endregion
|
|
5408
|
-
//#region src/features/server-path/utils/server-path-search.utils.ts
|
|
5409
|
-
function isServerPathInside(basePath, candidatePath) {
|
|
5410
|
-
const relativePath = relative(basePath, candidatePath);
|
|
5411
|
-
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
5412
|
-
}
|
|
5413
|
-
function normalizeServerPathRelativePath(value) {
|
|
5414
|
-
return value.split(sep).join("/");
|
|
5415
|
-
}
|
|
5416
|
-
function resolveServerPathSearchScore(entry, query) {
|
|
5417
|
-
if (!query) return 1;
|
|
5418
|
-
const normalizedQuery = query.toLocaleLowerCase();
|
|
5419
|
-
const normalizedName = entry.name.toLocaleLowerCase();
|
|
5420
|
-
const normalizedPath = entry.relativePath.toLocaleLowerCase();
|
|
5421
|
-
if (!normalizedQuery.split(/\s+/).filter(Boolean).every((term) => normalizedPath.includes(term))) return 0;
|
|
5422
|
-
if (normalizedName === normalizedQuery) return 1e3;
|
|
5423
|
-
if (normalizedName.startsWith(normalizedQuery)) return 850;
|
|
5424
|
-
if (normalizedName.includes(normalizedQuery)) return 700;
|
|
5425
|
-
if (normalizedPath.startsWith(normalizedQuery)) return 600;
|
|
5426
|
-
if (normalizedPath.split("/").some((segment) => segment.startsWith(normalizedQuery))) return 550;
|
|
5427
|
-
return 400 - Math.min(normalizedPath.length, 300);
|
|
5428
|
-
}
|
|
5429
|
-
//#endregion
|
|
5430
5458
|
//#region src/features/server-path/services/server-path-search.service.ts
|
|
5431
5459
|
const DEFAULT_RESULT_LIMIT = 50;
|
|
5432
5460
|
const MAX_RESULT_LIMIT = 100;
|
|
@@ -5614,6 +5642,199 @@ function isServerPathSearchError(error) {
|
|
|
5614
5642
|
return error instanceof ServerPathSearchError;
|
|
5615
5643
|
}
|
|
5616
5644
|
//#endregion
|
|
5645
|
+
//#region src/features/server-path/utils/server-path-mutation.utils.ts
|
|
5646
|
+
var ServerPathMutationError = class extends Error {
|
|
5647
|
+
constructor(code, message, details) {
|
|
5648
|
+
super(message);
|
|
5649
|
+
this.code = code;
|
|
5650
|
+
this.details = details;
|
|
5651
|
+
this.name = "ServerPathMutationError";
|
|
5652
|
+
}
|
|
5653
|
+
};
|
|
5654
|
+
function readFileErrorCode(error) {
|
|
5655
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : void 0;
|
|
5656
|
+
}
|
|
5657
|
+
function resolveScopedPaths(input) {
|
|
5658
|
+
let basePath;
|
|
5659
|
+
let targetPath;
|
|
5660
|
+
try {
|
|
5661
|
+
basePath = resolveServerPath({ path: typeof input.basePath === "string" ? input.basePath : null });
|
|
5662
|
+
targetPath = resolveServerPath({
|
|
5663
|
+
path: typeof input.targetPath === "string" ? input.targetPath : null,
|
|
5664
|
+
basePath
|
|
5665
|
+
});
|
|
5666
|
+
} catch (error) {
|
|
5667
|
+
if (error instanceof ServerPathResolutionError) throw new ServerPathMutationError("SERVER_PATH_BASE_INVALID", error.message);
|
|
5668
|
+
throw error;
|
|
5669
|
+
}
|
|
5670
|
+
if (!isServerPathInside(basePath, targetPath)) throw new ServerPathMutationError("SERVER_PATH_OUTSIDE_BASE", "target path must stay inside the project root");
|
|
5671
|
+
if (!input.allowRoot && targetPath === basePath) throw new ServerPathMutationError("SERVER_PATH_ROOT_PROTECTED", "the project root cannot be deleted");
|
|
5672
|
+
return {
|
|
5673
|
+
basePath,
|
|
5674
|
+
targetPath
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
async function resolveRealBasePath(basePath) {
|
|
5678
|
+
try {
|
|
5679
|
+
const resolved = await realpath(basePath);
|
|
5680
|
+
if (!(await stat(resolved)).isDirectory()) throw new ServerPathMutationError("SERVER_PATH_BASE_INVALID", "project root must point to a directory");
|
|
5681
|
+
return resolved;
|
|
5682
|
+
} catch (error) {
|
|
5683
|
+
if (error instanceof ServerPathMutationError) throw error;
|
|
5684
|
+
throw new ServerPathMutationError("SERVER_PATH_BASE_INVALID", "project root does not exist or is not accessible");
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
async function assertExistingPathInsideRealBase(params) {
|
|
5688
|
+
const [realBasePath, realTargetPath] = await Promise.all([resolveRealBasePath(params.basePath), realpath(params.targetPath).catch(() => null)]);
|
|
5689
|
+
if (!realTargetPath) throw new ServerPathMutationError("SERVER_PATH_ENTRY_NOT_FOUND", "target path does not exist");
|
|
5690
|
+
if (!isServerPathInside(realBasePath, realTargetPath)) throw new ServerPathMutationError("SERVER_PATH_OUTSIDE_BASE", "target path must stay inside the project root");
|
|
5691
|
+
}
|
|
5692
|
+
function normalizeEntryName(value) {
|
|
5693
|
+
const name = typeof value === "string" ? value.trim() : "";
|
|
5694
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || name.includes("\0") || basename(name) !== name) throw new ServerPathMutationError("SERVER_PATH_FILE_NAME_INVALID", "entry name is invalid");
|
|
5695
|
+
return name;
|
|
5696
|
+
}
|
|
5697
|
+
async function createServerPathFile(input) {
|
|
5698
|
+
const { basePath, targetPath: parentPath } = resolveScopedPaths({
|
|
5699
|
+
basePath: input.basePath,
|
|
5700
|
+
targetPath: input.parentPath,
|
|
5701
|
+
allowRoot: true
|
|
5702
|
+
});
|
|
5703
|
+
await assertExistingPathInsideRealBase({
|
|
5704
|
+
basePath,
|
|
5705
|
+
targetPath: parentPath
|
|
5706
|
+
});
|
|
5707
|
+
if (!(await stat(parentPath).catch(() => null))?.isDirectory()) throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_DIRECTORY", "file parent must point to a directory");
|
|
5708
|
+
const name = normalizeEntryName(input.name);
|
|
5709
|
+
const path = join(parentPath, name);
|
|
5710
|
+
try {
|
|
5711
|
+
await writeFile(path, "", { flag: "wx" });
|
|
5712
|
+
} catch (error) {
|
|
5713
|
+
const code = readFileErrorCode(error);
|
|
5714
|
+
if (code === "EEXIST") throw new ServerPathMutationError("SERVER_PATH_FILE_EXISTS", "target file already exists");
|
|
5715
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_WRITABLE", "file parent is not writable");
|
|
5716
|
+
throw error;
|
|
5717
|
+
}
|
|
5718
|
+
return {
|
|
5719
|
+
name,
|
|
5720
|
+
path,
|
|
5721
|
+
kind: "file"
|
|
5722
|
+
};
|
|
5723
|
+
}
|
|
5724
|
+
async function renameServerPathEntry(input) {
|
|
5725
|
+
const { basePath, targetPath } = resolveScopedPaths({
|
|
5726
|
+
basePath: input.basePath,
|
|
5727
|
+
targetPath: input.path
|
|
5728
|
+
});
|
|
5729
|
+
await assertExistingPathInsideRealBase({
|
|
5730
|
+
basePath,
|
|
5731
|
+
targetPath
|
|
5732
|
+
});
|
|
5733
|
+
const entryStats = await lstat(targetPath).catch(() => null);
|
|
5734
|
+
if (!entryStats) throw new ServerPathMutationError("SERVER_PATH_ENTRY_NOT_FOUND", "target path does not exist");
|
|
5735
|
+
const name = normalizeEntryName(input.name);
|
|
5736
|
+
const path = join(resolve(targetPath, ".."), name);
|
|
5737
|
+
if (!isServerPathInside(basePath, path)) throw new ServerPathMutationError("SERVER_PATH_OUTSIDE_BASE", "target path must stay inside the project root");
|
|
5738
|
+
if (await lstat(path).then(() => true).catch(() => false)) throw new ServerPathMutationError("SERVER_PATH_FILE_EXISTS", "target entry already exists");
|
|
5739
|
+
try {
|
|
5740
|
+
await rename(targetPath, path);
|
|
5741
|
+
} catch (error) {
|
|
5742
|
+
const code = readFileErrorCode(error);
|
|
5743
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_WRITABLE", "target path is not writable");
|
|
5744
|
+
throw error;
|
|
5745
|
+
}
|
|
5746
|
+
return {
|
|
5747
|
+
oldPath: targetPath,
|
|
5748
|
+
path,
|
|
5749
|
+
name,
|
|
5750
|
+
kind: entryStats.isDirectory() ? "directory" : "file"
|
|
5751
|
+
};
|
|
5752
|
+
}
|
|
5753
|
+
async function deleteServerPathEntry(input) {
|
|
5754
|
+
const { basePath, targetPath } = resolveScopedPaths({
|
|
5755
|
+
basePath: input.basePath,
|
|
5756
|
+
targetPath: input.path
|
|
5757
|
+
});
|
|
5758
|
+
await assertExistingPathInsideRealBase({
|
|
5759
|
+
basePath,
|
|
5760
|
+
targetPath
|
|
5761
|
+
});
|
|
5762
|
+
let entryStats;
|
|
5763
|
+
try {
|
|
5764
|
+
entryStats = await lstat(targetPath);
|
|
5765
|
+
} catch {
|
|
5766
|
+
throw new ServerPathMutationError("SERVER_PATH_ENTRY_NOT_FOUND", "target path does not exist");
|
|
5767
|
+
}
|
|
5768
|
+
const kind = entryStats.isDirectory() ? "directory" : "file";
|
|
5769
|
+
try {
|
|
5770
|
+
await rm(targetPath, {
|
|
5771
|
+
recursive: kind === "directory",
|
|
5772
|
+
force: false
|
|
5773
|
+
});
|
|
5774
|
+
} catch (error) {
|
|
5775
|
+
const code = readFileErrorCode(error);
|
|
5776
|
+
if (code === "ENOENT") throw new ServerPathMutationError("SERVER_PATH_ENTRY_NOT_FOUND", "target path does not exist");
|
|
5777
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_WRITABLE", "target path is not writable");
|
|
5778
|
+
throw error;
|
|
5779
|
+
}
|
|
5780
|
+
return {
|
|
5781
|
+
path: targetPath,
|
|
5782
|
+
kind
|
|
5783
|
+
};
|
|
5784
|
+
}
|
|
5785
|
+
async function uploadServerPathFiles(input) {
|
|
5786
|
+
const { basePath, targetPath } = resolveScopedPaths({
|
|
5787
|
+
basePath: input.basePath,
|
|
5788
|
+
targetPath: input.targetPath,
|
|
5789
|
+
allowRoot: true
|
|
5790
|
+
});
|
|
5791
|
+
await assertExistingPathInsideRealBase({
|
|
5792
|
+
basePath,
|
|
5793
|
+
targetPath
|
|
5794
|
+
});
|
|
5795
|
+
if (!(await stat(targetPath).catch(() => null))?.isDirectory()) throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_DIRECTORY", "upload target must point to a directory");
|
|
5796
|
+
if (input.files.length === 0) throw new ServerPathMutationError("SERVER_PATH_ENTRY_INVALID", "at least one upload file is required");
|
|
5797
|
+
const normalizedFiles = input.files.map((file) => ({
|
|
5798
|
+
file,
|
|
5799
|
+
name: normalizeEntryName(file.name)
|
|
5800
|
+
}));
|
|
5801
|
+
const uniqueNames = /* @__PURE__ */ new Set();
|
|
5802
|
+
for (const { name } of normalizedFiles) {
|
|
5803
|
+
if (uniqueNames.has(name)) throw new ServerPathMutationError("SERVER_PATH_FILE_NAME_INVALID", "upload file names must be unique");
|
|
5804
|
+
uniqueNames.add(name);
|
|
5805
|
+
}
|
|
5806
|
+
if (!input.overwrite) {
|
|
5807
|
+
const conflicts = (await Promise.all(normalizedFiles.map(async ({ name }) => {
|
|
5808
|
+
return await lstat(join(targetPath, name)).then(() => true).catch(() => false) ? name : null;
|
|
5809
|
+
}))).filter((name) => Boolean(name));
|
|
5810
|
+
if (conflicts.length > 0) throw new ServerPathMutationError("SERVER_PATH_FILE_EXISTS", "one or more upload files already exist", { conflicts });
|
|
5811
|
+
}
|
|
5812
|
+
try {
|
|
5813
|
+
return {
|
|
5814
|
+
files: await Promise.all(normalizedFiles.map(async ({ file, name }) => {
|
|
5815
|
+
const path = resolve(targetPath, name);
|
|
5816
|
+
const content = Buffer.from(await file.arrayBuffer());
|
|
5817
|
+
await writeFile(path, content, { flag: input.overwrite ? "w" : "wx" });
|
|
5818
|
+
return {
|
|
5819
|
+
name,
|
|
5820
|
+
path,
|
|
5821
|
+
sizeBytes: content.byteLength
|
|
5822
|
+
};
|
|
5823
|
+
})),
|
|
5824
|
+
overwritten: input.overwrite
|
|
5825
|
+
};
|
|
5826
|
+
} catch (error) {
|
|
5827
|
+
const code = readFileErrorCode(error);
|
|
5828
|
+
if (code === "EEXIST") throw new ServerPathMutationError("SERVER_PATH_FILE_EXISTS", "one or more upload files already exist");
|
|
5829
|
+
if (code === "ENOENT" || code === "ENOTDIR") throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_DIRECTORY", "upload target must point to an existing directory");
|
|
5830
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") throw new ServerPathMutationError("SERVER_PATH_TARGET_NOT_WRITABLE", "upload target is not writable");
|
|
5831
|
+
throw error;
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
function isServerPathMutationError(error) {
|
|
5835
|
+
return error instanceof ServerPathMutationError;
|
|
5836
|
+
}
|
|
5837
|
+
//#endregion
|
|
5617
5838
|
//#region src/features/server-path/controllers/server-path.controller.ts
|
|
5618
5839
|
function readIncludeFilesFlag(value) {
|
|
5619
5840
|
return value === "1" || value === "true";
|
|
@@ -5661,6 +5882,7 @@ var ServerPathRoutesController = class {
|
|
|
5661
5882
|
if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_DIRECTORY", "directory input is required"), 400);
|
|
5662
5883
|
try {
|
|
5663
5884
|
return c.json(ok(await createServerPathDirectory({
|
|
5885
|
+
basePath: body.data.basePath,
|
|
5664
5886
|
parentPath: body.data.parentPath,
|
|
5665
5887
|
name: body.data.name
|
|
5666
5888
|
})), 201);
|
|
@@ -5669,6 +5891,68 @@ var ServerPathRoutesController = class {
|
|
|
5669
5891
|
throw error;
|
|
5670
5892
|
}
|
|
5671
5893
|
};
|
|
5894
|
+
uploadFiles = async (c) => {
|
|
5895
|
+
let formData;
|
|
5896
|
+
try {
|
|
5897
|
+
formData = await c.req.raw.formData();
|
|
5898
|
+
} catch {
|
|
5899
|
+
return c.json(err("INVALID_SERVER_PATH_UPLOAD", "multipart upload is required"), 400);
|
|
5900
|
+
}
|
|
5901
|
+
const files = formData.getAll("files").flatMap((value) => typeof value === "string" ? [] : [{
|
|
5902
|
+
name: value.name,
|
|
5903
|
+
arrayBuffer: () => value.arrayBuffer()
|
|
5904
|
+
}]);
|
|
5905
|
+
try {
|
|
5906
|
+
return c.json(ok(await uploadServerPathFiles({
|
|
5907
|
+
basePath: formData.get("basePath"),
|
|
5908
|
+
targetPath: formData.get("targetPath"),
|
|
5909
|
+
overwrite: formData.get("overwrite") === "true",
|
|
5910
|
+
files
|
|
5911
|
+
})), 201);
|
|
5912
|
+
} catch (error) {
|
|
5913
|
+
if (isServerPathMutationError(error)) return c.json(err(error.code, error.message, error.details), error.code === "SERVER_PATH_FILE_EXISTS" ? 409 : 400);
|
|
5914
|
+
throw error;
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
createFile = async (c) => {
|
|
5918
|
+
const body = await readJson(c.req.raw);
|
|
5919
|
+
if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_FILE", "file input is required"), 400);
|
|
5920
|
+
try {
|
|
5921
|
+
return c.json(ok(await createServerPathFile({
|
|
5922
|
+
basePath: body.data.basePath,
|
|
5923
|
+
parentPath: body.data.parentPath,
|
|
5924
|
+
name: body.data.name
|
|
5925
|
+
})), 201);
|
|
5926
|
+
} catch (error) {
|
|
5927
|
+
if (isServerPathMutationError(error)) return c.json(err(error.code, error.message, error.details), error.code === "SERVER_PATH_FILE_EXISTS" ? 409 : 400);
|
|
5928
|
+
throw error;
|
|
5929
|
+
}
|
|
5930
|
+
};
|
|
5931
|
+
renameEntry = async (c) => {
|
|
5932
|
+
const body = await readJson(c.req.raw);
|
|
5933
|
+
if (!body.ok || !isRecord$2(body.data)) return c.json(err("INVALID_SERVER_PATH_RENAME", "rename input is required"), 400);
|
|
5934
|
+
try {
|
|
5935
|
+
return c.json(ok(await renameServerPathEntry({
|
|
5936
|
+
basePath: body.data.basePath,
|
|
5937
|
+
path: body.data.path,
|
|
5938
|
+
name: body.data.name
|
|
5939
|
+
})));
|
|
5940
|
+
} catch (error) {
|
|
5941
|
+
if (isServerPathMutationError(error)) return c.json(err(error.code, error.message, error.details), error.code === "SERVER_PATH_FILE_EXISTS" ? 409 : 400);
|
|
5942
|
+
throw error;
|
|
5943
|
+
}
|
|
5944
|
+
};
|
|
5945
|
+
deleteEntry = async (c) => {
|
|
5946
|
+
try {
|
|
5947
|
+
return c.json(ok(await deleteServerPathEntry({
|
|
5948
|
+
basePath: c.req.query("basePath"),
|
|
5949
|
+
path: c.req.query("path")
|
|
5950
|
+
})));
|
|
5951
|
+
} catch (error) {
|
|
5952
|
+
if (isServerPathMutationError(error)) return c.json(err(error.code, error.message, error.details), 400);
|
|
5953
|
+
throw error;
|
|
5954
|
+
}
|
|
5955
|
+
};
|
|
5672
5956
|
read = async (c) => {
|
|
5673
5957
|
try {
|
|
5674
5958
|
const payload = await readServerPath({
|
|
@@ -6095,6 +6379,26 @@ var UiRouteRegistry = class {
|
|
|
6095
6379
|
"/api/server-paths/directory",
|
|
6096
6380
|
serverPath.createDirectory
|
|
6097
6381
|
],
|
|
6382
|
+
[
|
|
6383
|
+
"post",
|
|
6384
|
+
"/api/server-paths/file",
|
|
6385
|
+
serverPath.createFile
|
|
6386
|
+
],
|
|
6387
|
+
[
|
|
6388
|
+
"post",
|
|
6389
|
+
"/api/server-paths/files",
|
|
6390
|
+
serverPath.uploadFiles
|
|
6391
|
+
],
|
|
6392
|
+
[
|
|
6393
|
+
"patch",
|
|
6394
|
+
"/api/server-paths/entry",
|
|
6395
|
+
serverPath.renameEntry
|
|
6396
|
+
],
|
|
6397
|
+
[
|
|
6398
|
+
"delete",
|
|
6399
|
+
"/api/server-paths/entry",
|
|
6400
|
+
serverPath.deleteEntry
|
|
6401
|
+
],
|
|
6098
6402
|
[
|
|
6099
6403
|
"get",
|
|
6100
6404
|
"/api/server-paths/read",
|