@aigne/afs 1.3.0 → 1.4.0-beta.10
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/CHANGELOG.md +106 -0
- package/lib/cjs/afs.d.ts +25 -28
- package/lib/cjs/afs.js +210 -43
- package/lib/cjs/error.d.ts +13 -0
- package/lib/cjs/error.js +25 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/type.d.ts +189 -38
- package/lib/cjs/type.js +23 -0
- package/lib/dts/afs.d.ts +25 -28
- package/lib/dts/error.d.ts +13 -0
- package/lib/dts/index.d.ts +1 -0
- package/lib/dts/type.d.ts +189 -38
- package/lib/esm/afs.d.ts +25 -28
- package/lib/esm/afs.js +210 -43
- package/lib/esm/error.d.ts +13 -0
- package/lib/esm/error.js +20 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/type.d.ts +189 -38
- package/lib/esm/type.js +22 -1
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,111 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0-beta.10](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.9...afs-v1.4.0-beta.10) (2026-01-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **afs:** improve list behavior to always include current path ([cb91f80](https://github.com/AIGNE-io/aigne-framework/commit/cb91f80c6ea3aa6e93dde26b6feeea8689fceb48))
|
|
9
|
+
|
|
10
|
+
## [1.4.0-beta.9](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.8...afs-v1.4.0-beta.9) (2026-01-14)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **afs:** add module access control and schema validation support ([#904](https://github.com/AIGNE-io/aigne-framework/issues/904)) ([d0b279a](https://github.com/AIGNE-io/aigne-framework/commit/d0b279aac07ebe2bcc1fd4148498fc3f6bbcd561))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* improve test coverage tracking and reporting ([#903](https://github.com/AIGNE-io/aigne-framework/issues/903)) ([031144e](https://github.com/AIGNE-io/aigne-framework/commit/031144e74f29e882cffe52ffda8f7a18c76ace7f))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Dependencies
|
|
24
|
+
|
|
25
|
+
* The following workspace dependencies were updated
|
|
26
|
+
* dependencies
|
|
27
|
+
* @aigne/platform-helpers bumped to 0.6.7-beta.2
|
|
28
|
+
|
|
29
|
+
## [1.4.0-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.7...afs-v1.4.0-beta.8) (2026-01-12)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Bug Fixes
|
|
33
|
+
|
|
34
|
+
* **afs:** show gitignored files with marker instead of filtering ([c2bdea1](https://github.com/AIGNE-io/aigne-framework/commit/c2bdea155f47c9420f2fe810cdfed79ef70ef899))
|
|
35
|
+
|
|
36
|
+
## [1.4.0-beta.7](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.6...afs-v1.4.0-beta.7) (2026-01-08)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Features
|
|
40
|
+
|
|
41
|
+
* **afs,bash:** add physical path mapping for AFS modules in bash execution ([#881](https://github.com/AIGNE-io/aigne-framework/issues/881)) ([50dbda2](https://github.com/AIGNE-io/aigne-framework/commit/50dbda224bd666d951494d2449779830d8db57fc))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Bug Fixes
|
|
45
|
+
|
|
46
|
+
* bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Dependencies
|
|
50
|
+
|
|
51
|
+
* The following workspace dependencies were updated
|
|
52
|
+
* dependencies
|
|
53
|
+
* @aigne/platform-helpers bumped to 0.6.7-beta.1
|
|
54
|
+
|
|
55
|
+
## [1.4.0-beta.6](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.5...afs-v1.4.0-beta.6) (2026-01-06)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* **afs:** throw errors instead of logging in AFS module operations ([#874](https://github.com/AIGNE-io/aigne-framework/issues/874)) ([f0cc1c4](https://github.com/AIGNE-io/aigne-framework/commit/f0cc1c4056f8b95b631d595892bb12eb75da4b9f))
|
|
61
|
+
|
|
62
|
+
## [1.4.0-beta.5](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.4...afs-v1.4.0-beta.5) (2025-12-31)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Features
|
|
66
|
+
|
|
67
|
+
* add session compact support for AIAgent ([#863](https://github.com/AIGNE-io/aigne-framework/issues/863)) ([9010918](https://github.com/AIGNE-io/aigne-framework/commit/9010918cd3f18b02b5c60ddc9ed5c34b568d0b28))
|
|
68
|
+
|
|
69
|
+
## [1.4.0-beta.4](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.3...afs-v1.4.0-beta.4) (2025-12-26)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Features
|
|
73
|
+
|
|
74
|
+
* **core:** add session history support ([#858](https://github.com/AIGNE-io/aigne-framework/issues/858)) ([28a070e](https://github.com/AIGNE-io/aigne-framework/commit/28a070ed33b821d1fd344b899706d817ca992b9f))
|
|
75
|
+
|
|
76
|
+
## [1.4.0-beta.3](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.2...afs-v1.4.0-beta.3) (2025-12-24)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
* add Agent Skill support ([#787](https://github.com/AIGNE-io/aigne-framework/issues/787)) ([f04fbe7](https://github.com/AIGNE-io/aigne-framework/commit/f04fbe76ec24cf3c59c74adf92d87b0c3784a8f7))
|
|
82
|
+
|
|
83
|
+
## [1.4.0-beta.2](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.1...afs-v1.4.0-beta.2) (2025-12-19)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Bug Fixes
|
|
87
|
+
|
|
88
|
+
* **afs:** use simple-list instead of tree as default type ([#839](https://github.com/AIGNE-io/aigne-framework/issues/839)) ([65a9a40](https://github.com/AIGNE-io/aigne-framework/commit/65a9a4054b3bdad6f7e40357299ef3dc48f7c3e4))
|
|
89
|
+
|
|
90
|
+
## [1.4.0-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta...afs-v1.4.0-beta.1) (2025-12-17)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### Bug Fixes
|
|
94
|
+
|
|
95
|
+
* bump version ([70d217c](https://github.com/AIGNE-io/aigne-framework/commit/70d217c8360dd0dda7f5f17011c4e92ec836e801))
|
|
96
|
+
|
|
97
|
+
## [1.4.0-beta](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.3.0...afs-v1.4.0-beta) (2025-12-17)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Features
|
|
101
|
+
|
|
102
|
+
* **afs:** support expand context into prompt template by call `$afs.xxx` ([#830](https://github.com/AIGNE-io/aigne-framework/issues/830)) ([5616acd](https://github.com/AIGNE-io/aigne-framework/commit/5616acd6ea257c91aa0b766608f45c5ce17f0345))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### Bug Fixes
|
|
106
|
+
|
|
107
|
+
* **afs:** read method should not throw not found error ([#835](https://github.com/AIGNE-io/aigne-framework/issues/835)) ([ebfdfc1](https://github.com/AIGNE-io/aigne-framework/commit/ebfdfc1cdba23efd23ac2ad4621e3f046990fd8b))
|
|
108
|
+
|
|
3
109
|
## [1.3.0](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.3.0-beta.3...afs-v1.3.0) (2025-12-12)
|
|
4
110
|
|
|
5
111
|
## [1.3.0-beta.3](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.3.0-beta.2...afs-v1.3.0-beta.3) (2025-12-11)
|
package/lib/cjs/afs.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Emitter } from "strict-event-emitter";
|
|
2
|
-
import type
|
|
2
|
+
import { type AFSContext, type AFSDeleteOptions, type AFSDeleteResult, type AFSExecOptions, type AFSExecResult, type AFSModule, type AFSReadOptions, type AFSReadResult, type AFSRenameOptions, type AFSRenameResult, type AFSRoot, type AFSRootEvents, type AFSRootListOptions, type AFSRootListResult, type AFSRootSearchOptions, type AFSRootSearchResult, type AFSWriteEntryPayload, type AFSWriteOptions, type AFSWriteResult } from "./type.js";
|
|
3
3
|
export interface AFSOptions {
|
|
4
4
|
modules?: AFSModule[];
|
|
5
|
+
context?: AFSContext;
|
|
5
6
|
}
|
|
6
7
|
export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
8
|
+
options: AFSOptions;
|
|
7
9
|
name: string;
|
|
8
10
|
constructor(options?: AFSOptions);
|
|
9
11
|
private modules;
|
|
12
|
+
/**
|
|
13
|
+
* Check if write operations are allowed for the given module.
|
|
14
|
+
* Throws AFSReadonlyError if not allowed.
|
|
15
|
+
*/
|
|
16
|
+
private checkWritePermission;
|
|
10
17
|
mount(module: AFSModule): this;
|
|
11
18
|
listModules(): Promise<{
|
|
12
19
|
name: string;
|
|
@@ -14,32 +21,22 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
14
21
|
description?: string;
|
|
15
22
|
module: AFSModule;
|
|
16
23
|
}[]>;
|
|
17
|
-
list(path: string, options?:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
message?: string;
|
|
28
|
-
}>;
|
|
29
|
-
delete(path: string, options?: AFSDeleteOptions): Promise<{
|
|
30
|
-
message?: string;
|
|
31
|
-
}>;
|
|
32
|
-
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<{
|
|
33
|
-
message?: string;
|
|
34
|
-
}>;
|
|
35
|
-
search(path: string, query: string, options?: AFSSearchOptions): Promise<{
|
|
36
|
-
list: AFSEntry[];
|
|
37
|
-
message?: string;
|
|
38
|
-
}>;
|
|
24
|
+
list(path: string, options?: AFSRootListOptions): Promise<AFSRootListResult>;
|
|
25
|
+
private _list;
|
|
26
|
+
read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
27
|
+
write(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
|
|
28
|
+
delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
|
|
29
|
+
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<AFSRenameResult>;
|
|
30
|
+
search(path: string, query: string, options?: AFSRootSearchOptions): Promise<AFSRootSearchResult>;
|
|
31
|
+
private processWithPreset;
|
|
32
|
+
private _select;
|
|
33
|
+
private _search;
|
|
39
34
|
private findModules;
|
|
40
|
-
exec(path: string, args: Record<string, any>, options:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
exec(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
36
|
+
private buildSimpleListView;
|
|
37
|
+
private buildTreeView;
|
|
38
|
+
private buildMetadataSuffix;
|
|
39
|
+
private physicalPath?;
|
|
40
|
+
initializePhysicalPath(): Promise<string>;
|
|
41
|
+
cleanupPhysicalPath(): Promise<void>;
|
|
45
42
|
}
|
package/lib/cjs/afs.js
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AFS = void 0;
|
|
4
|
+
const index_js_1 = require("@aigne/platform-helpers/nodejs/index.js");
|
|
5
|
+
const uuid_1 = require("@aigne/uuid");
|
|
4
6
|
const strict_event_emitter_1 = require("strict-event-emitter");
|
|
5
7
|
const ufo_1 = require("ufo");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const error_js_1 = require("./error.js");
|
|
10
|
+
const type_js_1 = require("./type.js");
|
|
6
11
|
const DEFAULT_MAX_DEPTH = 1;
|
|
7
12
|
const MODULES_ROOT_DIR = "/modules";
|
|
8
13
|
class AFS extends strict_event_emitter_1.Emitter {
|
|
14
|
+
options;
|
|
9
15
|
name = "AFSRoot";
|
|
10
|
-
constructor(options) {
|
|
16
|
+
constructor(options = {}) {
|
|
11
17
|
super();
|
|
18
|
+
this.options = options;
|
|
12
19
|
for (const module of options?.modules ?? []) {
|
|
13
20
|
this.mount(module);
|
|
14
21
|
}
|
|
15
22
|
}
|
|
16
23
|
modules = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* Check if write operations are allowed for the given module.
|
|
26
|
+
* Throws AFSReadonlyError if not allowed.
|
|
27
|
+
*/
|
|
28
|
+
checkWritePermission(module, operation, path) {
|
|
29
|
+
// Module-level readonly (undefined means readonly by default)
|
|
30
|
+
if (module.accessMode !== "readwrite") {
|
|
31
|
+
throw new error_js_1.AFSReadonlyError(`Module '${module.name}' is readonly, cannot perform ${operation} to ${path}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
17
34
|
mount(module) {
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
throw new Error(`Invalid
|
|
35
|
+
// Validate module name (should not contain '/')
|
|
36
|
+
if (module.name.includes("/")) {
|
|
37
|
+
throw new Error(`Invalid module name: ${module.name}. Module name must not contain '/'`);
|
|
21
38
|
}
|
|
22
|
-
path = (0, ufo_1.joinURL)(MODULES_ROOT_DIR,
|
|
39
|
+
const path = (0, ufo_1.joinURL)(MODULES_ROOT_DIR, module.name);
|
|
23
40
|
if (this.modules.has(path)) {
|
|
24
41
|
throw new Error(`Module already mounted at path: ${path}`);
|
|
25
42
|
}
|
|
@@ -35,74 +52,101 @@ class AFS extends strict_event_emitter_1.Emitter {
|
|
|
35
52
|
module,
|
|
36
53
|
}));
|
|
37
54
|
}
|
|
38
|
-
async list(path, options) {
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
55
|
+
async list(path, options = {}) {
|
|
56
|
+
let preset;
|
|
57
|
+
if (options.preset) {
|
|
58
|
+
preset = this.options?.context?.list?.presets?.[options.preset];
|
|
59
|
+
if (!preset)
|
|
60
|
+
throw new Error(`Preset not found: ${options.preset}`);
|
|
61
|
+
}
|
|
62
|
+
return await this.processWithPreset(path, undefined, preset, {
|
|
63
|
+
...options,
|
|
64
|
+
defaultSelect: () => this._list(path, options),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async _list(path, options = {}) {
|
|
42
68
|
const results = [];
|
|
43
|
-
|
|
69
|
+
// Special case: listing root "/" should return /modules directory
|
|
70
|
+
if (path === "/" && this.modules.size > 0) {
|
|
71
|
+
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
72
|
+
// Always include /modules directory first
|
|
73
|
+
results.push({
|
|
74
|
+
id: "modules",
|
|
75
|
+
path: MODULES_ROOT_DIR,
|
|
76
|
+
summary: "All mounted modules",
|
|
77
|
+
});
|
|
78
|
+
if (maxDepth === 1) {
|
|
79
|
+
// Only show /modules directory
|
|
80
|
+
return { data: results };
|
|
81
|
+
}
|
|
82
|
+
// For maxDepth > 1, also get children of /modules with reduced depth
|
|
83
|
+
const childrenResult = await this._list(MODULES_ROOT_DIR, {
|
|
84
|
+
...options,
|
|
85
|
+
maxDepth: maxDepth - 1,
|
|
86
|
+
});
|
|
87
|
+
results.push(...childrenResult.data);
|
|
88
|
+
return { data: results };
|
|
89
|
+
}
|
|
44
90
|
const matches = this.findModules(path, options);
|
|
45
91
|
for (const matched of matches) {
|
|
46
|
-
const moduleEntry = {
|
|
47
|
-
id: matched.module.name,
|
|
48
|
-
path: matched.remainedModulePath,
|
|
49
|
-
summary: matched.module.description,
|
|
50
|
-
};
|
|
51
92
|
if (matched.maxDepth === 0) {
|
|
93
|
+
// When maxDepth is 0, show the module entry
|
|
94
|
+
const moduleEntry = {
|
|
95
|
+
id: matched.module.name,
|
|
96
|
+
path: matched.modulePath,
|
|
97
|
+
summary: matched.module.description,
|
|
98
|
+
};
|
|
52
99
|
results.push(moduleEntry);
|
|
53
100
|
continue;
|
|
54
101
|
}
|
|
55
102
|
if (!matched.module.list)
|
|
56
103
|
continue;
|
|
57
104
|
try {
|
|
58
|
-
const {
|
|
105
|
+
const { data } = await matched.module.list(matched.subpath, {
|
|
59
106
|
...options,
|
|
60
107
|
maxDepth: matched.maxDepth,
|
|
61
108
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
results.push(moduleEntry);
|
|
70
|
-
}
|
|
71
|
-
if (message)
|
|
72
|
-
messages.push(message);
|
|
109
|
+
const children = data.map((entry) => ({
|
|
110
|
+
...entry,
|
|
111
|
+
path: (0, ufo_1.joinURL)(matched.modulePath, entry.path),
|
|
112
|
+
}));
|
|
113
|
+
// Always include all nodes (including the current path itself)
|
|
114
|
+
// This ensures consistent behavior across all listing scenarios
|
|
115
|
+
results.push(...children);
|
|
73
116
|
}
|
|
74
117
|
catch (error) {
|
|
75
|
-
|
|
118
|
+
throw new Error(`Error listing from module at ${matched.modulePath}: ${error.message}`);
|
|
76
119
|
}
|
|
77
120
|
}
|
|
78
|
-
return {
|
|
121
|
+
return { data: results };
|
|
79
122
|
}
|
|
80
|
-
async read(path) {
|
|
123
|
+
async read(path, _options) {
|
|
81
124
|
const modules = this.findModules(path, { exactMatch: true });
|
|
82
125
|
for (const { module, modulePath, subpath } of modules) {
|
|
83
126
|
const res = await module.read?.(subpath);
|
|
84
|
-
if (res?.
|
|
127
|
+
if (res?.data) {
|
|
85
128
|
return {
|
|
86
129
|
...res,
|
|
87
|
-
|
|
88
|
-
...res.
|
|
89
|
-
path: (0, ufo_1.joinURL)(modulePath, res.
|
|
130
|
+
data: {
|
|
131
|
+
...res.data,
|
|
132
|
+
path: (0, ufo_1.joinURL)(modulePath, res.data.path),
|
|
90
133
|
},
|
|
91
134
|
};
|
|
92
135
|
}
|
|
93
136
|
}
|
|
94
|
-
return {
|
|
137
|
+
return { data: undefined, message: "File not found" };
|
|
95
138
|
}
|
|
96
139
|
async write(path, content, options) {
|
|
97
140
|
const module = this.findModules(path, { exactMatch: true })[0];
|
|
98
141
|
if (!module?.module.write)
|
|
99
142
|
throw new Error(`No module found for path: ${path}`);
|
|
143
|
+
this.checkWritePermission(module.module, "write", path);
|
|
100
144
|
const res = await module.module.write(module.subpath, content, options);
|
|
101
145
|
return {
|
|
102
146
|
...res,
|
|
103
|
-
|
|
104
|
-
...res.
|
|
105
|
-
path: (0, ufo_1.joinURL)(module.modulePath, res.
|
|
147
|
+
data: {
|
|
148
|
+
...res.data,
|
|
149
|
+
path: (0, ufo_1.joinURL)(module.modulePath, res.data.path),
|
|
106
150
|
},
|
|
107
151
|
};
|
|
108
152
|
}
|
|
@@ -110,6 +154,7 @@ class AFS extends strict_event_emitter_1.Emitter {
|
|
|
110
154
|
const module = this.findModules(path, { exactMatch: true })[0];
|
|
111
155
|
if (!module?.module.delete)
|
|
112
156
|
throw new Error(`No module found for path: ${path}`);
|
|
157
|
+
this.checkWritePermission(module.module, "delete", path);
|
|
113
158
|
return await module.module.delete(module.subpath, options);
|
|
114
159
|
}
|
|
115
160
|
async rename(oldPath, newPath, options) {
|
|
@@ -122,17 +167,64 @@ class AFS extends strict_event_emitter_1.Emitter {
|
|
|
122
167
|
if (!oldModule.module.rename) {
|
|
123
168
|
throw new Error(`Module does not support rename operation: ${oldModule.modulePath}`);
|
|
124
169
|
}
|
|
170
|
+
this.checkWritePermission(oldModule.module, "rename", oldPath);
|
|
125
171
|
return await oldModule.module.rename(oldModule.subpath, newModule.subpath, options);
|
|
126
172
|
}
|
|
127
|
-
async search(path, query, options) {
|
|
173
|
+
async search(path, query, options = {}) {
|
|
174
|
+
let preset;
|
|
175
|
+
if (options.preset) {
|
|
176
|
+
preset = this.options?.context?.search?.presets?.[options.preset];
|
|
177
|
+
if (!preset)
|
|
178
|
+
throw new Error(`Preset not found: ${options.preset}`);
|
|
179
|
+
}
|
|
180
|
+
return await this.processWithPreset(path, query, preset, {
|
|
181
|
+
...options,
|
|
182
|
+
defaultSelect: () => this._search(path, query, options),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async processWithPreset(path, query, preset, options) {
|
|
186
|
+
const select = options.select || preset?.select;
|
|
187
|
+
const per = options.per || preset?.per;
|
|
188
|
+
const dedupe = options.dedupe || preset?.dedupe;
|
|
189
|
+
const format = options.format || preset?.format;
|
|
190
|
+
const entries = select
|
|
191
|
+
? (await this._select(path, query, select, options)).data
|
|
192
|
+
: (await options.defaultSelect()).data;
|
|
193
|
+
const mapped = per
|
|
194
|
+
? await Promise.all(entries.map((data) => per.invoke({ data }, options).then((res) => res.data)))
|
|
195
|
+
: entries;
|
|
196
|
+
const deduped = dedupe
|
|
197
|
+
? await dedupe.invoke({ data: mapped }, options).then((res) => res.data)
|
|
198
|
+
: mapped;
|
|
199
|
+
let formatted = deduped;
|
|
200
|
+
if (format === "simple-list" || format === "tree") {
|
|
201
|
+
const valid = zod_1.z.array(type_js_1.afsEntrySchema).safeParse(deduped);
|
|
202
|
+
if (!valid.data)
|
|
203
|
+
throw new Error("Tree format requires entries to be AFSEntry objects");
|
|
204
|
+
if (format === "tree")
|
|
205
|
+
formatted = this.buildTreeView(valid.data);
|
|
206
|
+
else if (format === "simple-list")
|
|
207
|
+
formatted = this.buildSimpleListView(valid.data);
|
|
208
|
+
}
|
|
209
|
+
else if (typeof format === "object" && typeof format.invoke === "function") {
|
|
210
|
+
formatted = await format.invoke({ data: deduped }, options).then((res) => res.data);
|
|
211
|
+
}
|
|
212
|
+
return { data: formatted };
|
|
213
|
+
}
|
|
214
|
+
async _select(path, query, select, options) {
|
|
215
|
+
const { data } = await select.invoke({ path, query }, options);
|
|
216
|
+
const results = (await Promise.all(data.map((p) => this.read(p).then((res) => res.data)))).filter((i) => !!i);
|
|
217
|
+
return { data: results };
|
|
218
|
+
}
|
|
219
|
+
async _search(path, query, options) {
|
|
128
220
|
const results = [];
|
|
129
221
|
const messages = [];
|
|
130
222
|
for (const { module, modulePath, subpath } of this.findModules(path)) {
|
|
131
223
|
if (!module.search)
|
|
132
224
|
continue;
|
|
133
225
|
try {
|
|
134
|
-
const {
|
|
135
|
-
results.push(...
|
|
226
|
+
const { data, message } = await module.search(subpath, query, options);
|
|
227
|
+
results.push(...data.map((entry) => ({
|
|
136
228
|
...entry,
|
|
137
229
|
path: (0, ufo_1.joinURL)(modulePath, entry.path),
|
|
138
230
|
})));
|
|
@@ -140,10 +232,10 @@ class AFS extends strict_event_emitter_1.Emitter {
|
|
|
140
232
|
messages.push(message);
|
|
141
233
|
}
|
|
142
234
|
catch (error) {
|
|
143
|
-
|
|
235
|
+
throw new Error(`Error searching in module at ${modulePath}: ${error.message}`);
|
|
144
236
|
}
|
|
145
237
|
}
|
|
146
|
-
return {
|
|
238
|
+
return { data: results, message: messages.join("; ") };
|
|
147
239
|
}
|
|
148
240
|
findModules(path, options) {
|
|
149
241
|
const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
|
|
@@ -179,5 +271,80 @@ class AFS extends strict_event_emitter_1.Emitter {
|
|
|
179
271
|
throw new Error(`No module found for path: ${path}`);
|
|
180
272
|
return await module.module.exec(module.subpath, args, options);
|
|
181
273
|
}
|
|
274
|
+
buildSimpleListView(entries) {
|
|
275
|
+
return entries.map((entry) => `${entry.path}${this.buildMetadataSuffix(entry)}`);
|
|
276
|
+
}
|
|
277
|
+
buildTreeView(entries) {
|
|
278
|
+
const tree = {};
|
|
279
|
+
const entryMap = new Map();
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
entryMap.set(entry.path, entry);
|
|
282
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
283
|
+
let current = tree;
|
|
284
|
+
for (const part of parts) {
|
|
285
|
+
if (!current[part]) {
|
|
286
|
+
current[part] = {};
|
|
287
|
+
}
|
|
288
|
+
current = current[part];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const renderTree = (node, prefix = "", currentPath = "") => {
|
|
292
|
+
let result = "";
|
|
293
|
+
const keys = Object.keys(node);
|
|
294
|
+
keys.forEach((key, index) => {
|
|
295
|
+
const isLast = index === keys.length - 1;
|
|
296
|
+
const fullPath = currentPath ? `${currentPath}/${key}` : `/${key}`;
|
|
297
|
+
const entry = entryMap.get(fullPath);
|
|
298
|
+
result += `${prefix}${isLast ? "└── " : "├── "}${key}${entry ? this.buildMetadataSuffix(entry) : ""}`;
|
|
299
|
+
result += `\n`;
|
|
300
|
+
result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`, fullPath);
|
|
301
|
+
});
|
|
302
|
+
return result;
|
|
303
|
+
};
|
|
304
|
+
return renderTree(tree);
|
|
305
|
+
}
|
|
306
|
+
buildMetadataSuffix(entry) {
|
|
307
|
+
// Build metadata suffix
|
|
308
|
+
const metadataParts = [];
|
|
309
|
+
// Children count
|
|
310
|
+
const childrenCount = entry?.metadata?.childrenCount;
|
|
311
|
+
if (typeof childrenCount === "number") {
|
|
312
|
+
metadataParts.push(`${childrenCount} items`);
|
|
313
|
+
}
|
|
314
|
+
// Children truncated
|
|
315
|
+
if (entry?.metadata?.childrenTruncated) {
|
|
316
|
+
metadataParts.push("truncated");
|
|
317
|
+
}
|
|
318
|
+
// Gitignored
|
|
319
|
+
if (entry?.metadata?.gitignored) {
|
|
320
|
+
metadataParts.push("gitignored");
|
|
321
|
+
}
|
|
322
|
+
// Executable
|
|
323
|
+
if (entry?.metadata?.execute) {
|
|
324
|
+
metadataParts.push("executable");
|
|
325
|
+
}
|
|
326
|
+
const metadataSuffix = metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
|
|
327
|
+
return metadataSuffix;
|
|
328
|
+
}
|
|
329
|
+
physicalPath;
|
|
330
|
+
async initializePhysicalPath() {
|
|
331
|
+
this.physicalPath ??= (async () => {
|
|
332
|
+
const rootDir = index_js_1.nodejs.path.join(index_js_1.nodejs.os.tmpdir(), (0, uuid_1.v7)());
|
|
333
|
+
await index_js_1.nodejs.fs.mkdir(rootDir, { recursive: true });
|
|
334
|
+
for (const [modulePath, module] of this.modules) {
|
|
335
|
+
const physicalModulePath = index_js_1.nodejs.path.join(rootDir, modulePath);
|
|
336
|
+
await index_js_1.nodejs.fs.mkdir(index_js_1.nodejs.path.dirname(physicalModulePath), { recursive: true });
|
|
337
|
+
await module.symlinkToPhysical?.(physicalModulePath);
|
|
338
|
+
}
|
|
339
|
+
return rootDir;
|
|
340
|
+
})();
|
|
341
|
+
return this.physicalPath;
|
|
342
|
+
}
|
|
343
|
+
async cleanupPhysicalPath() {
|
|
344
|
+
if (this.physicalPath) {
|
|
345
|
+
await index_js_1.nodejs.fs.rm(await this.physicalPath, { recursive: true, force: true });
|
|
346
|
+
this.physicalPath = undefined;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
182
349
|
}
|
|
183
350
|
exports.AFS = AFS;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all AFS errors.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AFSError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
constructor(message: string, code: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when attempting write operations on a readonly AFS or module.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AFSReadonlyError extends AFSError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
package/lib/cjs/error.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AFSReadonlyError = exports.AFSError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all AFS errors.
|
|
6
|
+
*/
|
|
7
|
+
class AFSError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "AFSError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.AFSError = AFSError;
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when attempting write operations on a readonly AFS or module.
|
|
18
|
+
*/
|
|
19
|
+
class AFSReadonlyError extends AFSError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message, "AFS_READONLY");
|
|
22
|
+
this.name = "AFSReadonlyError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.AFSReadonlyError = AFSReadonlyError;
|
package/lib/cjs/index.d.ts
CHANGED
package/lib/cjs/index.js
CHANGED
|
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./afs.js"), exports);
|
|
18
|
+
__exportStar(require("./error.js"), exports);
|
|
18
19
|
__exportStar(require("./type.js"), exports);
|