nexus-agentd 0.1.8 → 0.2.0

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.
@@ -1,269 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import { createWriteStream } from 'node:fs';
3
- import { lstat, mkdir, open as openFile, readdir, realpath, rename, rm, stat } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { Transform } from 'node:stream';
6
- import { pipeline } from 'node:stream/promises';
7
- export class WorkspaceFileError extends Error {
8
- status;
9
- constructor(status, message) {
10
- super(message);
11
- this.status = status;
12
- }
13
- }
14
- export class WorkspaceFiles {
15
- configuredRoots;
16
- maxWriteBytes;
17
- constructor(configuredRoots, maxWriteBytes) {
18
- this.configuredRoots = configuredRoots;
19
- this.maxWriteBytes = maxWriteBytes;
20
- }
21
- async roots() {
22
- const values = await Promise.all(this.configuredRoots().map(async (configured) => {
23
- const rootPath = await realpath(configured);
24
- return {
25
- id: rootId(rootPath),
26
- name: path.basename(rootPath) || rootPath,
27
- path: rootPath
28
- };
29
- }));
30
- return Array.from(new Map(values.map((root) => [root.id, root])).values());
31
- }
32
- async adminRoot(id) {
33
- const root = (await this.roots()).find((item) => item.id === id);
34
- if (!root)
35
- throw new WorkspaceFileError(404, 'Workspace root not found');
36
- return root.path;
37
- }
38
- async sessionRoot(workspace) {
39
- if (!workspace)
40
- throw new WorkspaceFileError(409, 'Session has no local workspace');
41
- const candidate = await realpath(workspace);
42
- const roots = await this.roots();
43
- if (!roots.some((root) => isWithin(root.path, candidate))) {
44
- throw new WorkspaceFileError(403, 'Session workspace is outside the configured roots');
45
- }
46
- return candidate;
47
- }
48
- async list(root, relative = '') {
49
- const directory = await this.existing(root, relative, true);
50
- const details = await stat(directory);
51
- if (!details.isDirectory())
52
- throw new WorkspaceFileError(400, 'Path is not a directory');
53
- const entries = await readdir(directory, { withFileTypes: true });
54
- const result = await Promise.all(entries.map(async (entry) => {
55
- const filePath = path.join(directory, entry.name);
56
- const info = await lstat(filePath);
57
- const type = entry.isDirectory()
58
- ? 'directory'
59
- : entry.isFile()
60
- ? 'file'
61
- : entry.isSymbolicLink()
62
- ? 'symlink'
63
- : 'other';
64
- return {
65
- name: entry.name,
66
- path: joinRelative(relative, entry.name),
67
- type,
68
- ...(type === 'file' ? { size: info.size } : {}),
69
- modifiedAt: info.mtimeMs
70
- };
71
- }));
72
- result.sort((left, right) => {
73
- if (left.type === 'directory' && right.type !== 'directory')
74
- return -1;
75
- if (right.type === 'directory' && left.type !== 'directory')
76
- return 1;
77
- return left.name.localeCompare(right.name);
78
- });
79
- return { path: normalizeRelative(relative, true), entries: result };
80
- }
81
- async open(root, relative) {
82
- const rootPath = await realpath(root);
83
- const normalized = normalizeRelative(relative, false);
84
- const candidate = path.join(rootPath, normalized);
85
- let before;
86
- try {
87
- before = await realpath(candidate);
88
- }
89
- catch (error) {
90
- throwPathError(error);
91
- }
92
- if (!isWithin(rootPath, before)) {
93
- throw new WorkspaceFileError(403, 'Path leaves the workspace root');
94
- }
95
- let handle;
96
- try {
97
- handle = await openFile(candidate, 'r');
98
- }
99
- catch (error) {
100
- throwPathError(error);
101
- }
102
- try {
103
- const opened = await handle.stat({ bigint: true });
104
- if (!opened.isFile())
105
- throw new WorkspaceFileError(400, 'Path is not a file');
106
- let after;
107
- try {
108
- after = await realpath(candidate);
109
- }
110
- catch (error) {
111
- if (isMissingPathError(error)) {
112
- throw new WorkspaceFileError(409, 'File changed while it was being opened');
113
- }
114
- throw error;
115
- }
116
- if (!isWithin(rootPath, after)) {
117
- throw new WorkspaceFileError(403, 'Path leaves the workspace root');
118
- }
119
- const current = await stat(after, { bigint: true });
120
- if (opened.dev !== current.dev || opened.ino !== current.ino) {
121
- throw new WorkspaceFileError(409, 'File changed while it was being opened');
122
- }
123
- return {
124
- handle,
125
- filePath: after,
126
- name: path.basename(normalized),
127
- size: Number(opened.size)
128
- };
129
- }
130
- catch (error) {
131
- await handle.close();
132
- throw error;
133
- }
134
- }
135
- async publishable(root, input) {
136
- const relative = path.isAbsolute(input) ? path.relative(root, input) : input;
137
- return this.open(root, relative);
138
- }
139
- async write(root, relative, input, declaredSize) {
140
- if (declaredSize !== undefined && declaredSize > this.maxWriteBytes) {
141
- throw new WorkspaceFileError(413, 'Upload exceeds the file size limit');
142
- }
143
- const target = await this.writable(root, relative);
144
- const temporary = path.join(path.dirname(target), `.nexus-upload-${randomUUID()}`);
145
- let size = 0;
146
- const meter = new Transform({
147
- transform: (chunk, _encoding, callback) => {
148
- size += chunk.length;
149
- if (size > this.maxWriteBytes) {
150
- callback(new WorkspaceFileError(413, 'Upload exceeds the file size limit'));
151
- return;
152
- }
153
- callback(null, chunk);
154
- }
155
- });
156
- try {
157
- await pipeline(input, meter, createWriteStream(temporary, { flags: 'wx', mode: 0o600 }));
158
- await rename(temporary, target);
159
- return { path: normalizeRelative(relative, false), size };
160
- }
161
- catch (error) {
162
- await rm(temporary, { force: true }).catch(() => undefined);
163
- throw error;
164
- }
165
- }
166
- async createDirectory(root, relative) {
167
- const target = await this.writable(root, relative);
168
- await mkdir(target);
169
- return { path: normalizeRelative(relative, false) };
170
- }
171
- async move(root, source, destination) {
172
- const sourcePath = await this.existing(root, source, false, false);
173
- const destinationPath = await this.writable(root, destination);
174
- try {
175
- await lstat(destinationPath);
176
- throw new WorkspaceFileError(409, 'Destination already exists');
177
- }
178
- catch (error) {
179
- if (error.code !== 'ENOENT')
180
- throw error;
181
- }
182
- await rename(sourcePath, destinationPath);
183
- return {
184
- path: normalizeRelative(source, false),
185
- destination: normalizeRelative(destination, false)
186
- };
187
- }
188
- async remove(root, relative) {
189
- const target = await this.existing(root, relative, false, false);
190
- await rm(target, { recursive: true, force: false });
191
- return { path: normalizeRelative(relative, false) };
192
- }
193
- async existing(root, relative, allowEmpty, follow = true) {
194
- const rootPath = await realpath(root);
195
- const normalized = normalizeRelative(relative, allowEmpty);
196
- const candidate = path.join(rootPath, normalized);
197
- let resolved;
198
- try {
199
- resolved = follow ? await realpath(candidate) : await resolveParent(candidate);
200
- }
201
- catch (error) {
202
- throwPathError(error);
203
- }
204
- if (!isWithin(rootPath, resolved))
205
- throw new WorkspaceFileError(403, 'Path leaves the workspace root');
206
- return resolved;
207
- }
208
- async writable(root, relative) {
209
- const rootPath = await realpath(root);
210
- const normalized = normalizeRelative(relative, false);
211
- const candidate = path.join(rootPath, normalized);
212
- let parent;
213
- try {
214
- parent = await realpath(path.dirname(candidate));
215
- }
216
- catch (error) {
217
- throwPathError(error);
218
- }
219
- if (!isWithin(rootPath, parent))
220
- throw new WorkspaceFileError(403, 'Path leaves the workspace root');
221
- return path.join(parent, path.basename(candidate));
222
- }
223
- }
224
- export function workspaceRootId(root) {
225
- return rootId(path.resolve(root));
226
- }
227
- function normalizeRelative(value, allowEmpty) {
228
- const input = String(value || '').replace(/[\\/]+/g, path.sep);
229
- if (input.includes('\0') || path.isAbsolute(input) || /^[a-zA-Z]:/.test(input)) {
230
- throw new WorkspaceFileError(400, 'Path must be relative to the workspace root');
231
- }
232
- const normalized = path.normalize(input || '.');
233
- if (normalized === '..' || normalized.startsWith(`..${path.sep}`)) {
234
- throw new WorkspaceFileError(403, 'Path leaves the workspace root');
235
- }
236
- if (normalized === '.') {
237
- if (allowEmpty)
238
- return '';
239
- throw new WorkspaceFileError(400, 'A file or directory path is required');
240
- }
241
- return normalized;
242
- }
243
- async function resolveParent(candidate) {
244
- const parent = await realpath(path.dirname(candidate));
245
- const target = path.join(parent, path.basename(candidate));
246
- await lstat(target);
247
- return target;
248
- }
249
- function rootId(value) {
250
- return createHash('sha256').update(path.resolve(value)).digest('hex').slice(0, 24);
251
- }
252
- function isWithin(root, candidate) {
253
- const relative = path.relative(root, candidate);
254
- return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
255
- }
256
- function joinRelative(parent, name) {
257
- const normalized = normalizeRelative(parent, true);
258
- return normalized ? path.join(normalized, name) : name;
259
- }
260
- function isMissingPathError(error) {
261
- const code = error.code;
262
- return code === 'ENOENT' || code === 'ENOTDIR';
263
- }
264
- function throwPathError(error) {
265
- if (isMissingPathError(error)) {
266
- throw new WorkspaceFileError(404, 'File or directory not found');
267
- }
268
- throw error;
269
- }