@fgv/ts-json-base 5.0.2 → 5.1.0-1
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/packlets/converters/converters.js +36 -14
- package/dist/packlets/file-tree/directoryItem.js +99 -4
- package/dist/packlets/file-tree/fileItem.js +47 -9
- package/dist/packlets/file-tree/fileTreeAccessors.js +59 -1
- package/dist/packlets/file-tree/filterSpec.js +74 -0
- package/dist/packlets/file-tree/fsTree.js +107 -12
- package/dist/packlets/file-tree/in-memory/inMemoryTree.js +279 -21
- package/dist/packlets/file-tree/in-memory/treeBuilder.js +31 -0
- package/dist/packlets/file-tree/index.browser.js +1 -0
- package/dist/packlets/file-tree/index.js +1 -0
- package/dist/packlets/json-file/file.js +1 -1
- package/dist/packlets/json-file/jsonFsHelper.js +1 -1
- package/dist/packlets/validators/validators.js +8 -8
- package/dist/ts-json-base.d.ts +439 -65
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/packlets/converters/converters.d.ts +20 -13
- package/lib/packlets/converters/converters.js +36 -13
- package/lib/packlets/file-tree/directoryItem.d.ts +29 -6
- package/lib/packlets/file-tree/directoryItem.js +98 -3
- package/lib/packlets/file-tree/fileItem.d.ts +31 -14
- package/lib/packlets/file-tree/fileItem.js +46 -8
- package/lib/packlets/file-tree/fileTreeAccessors.d.ts +237 -3
- package/lib/packlets/file-tree/fileTreeAccessors.js +63 -0
- package/lib/packlets/file-tree/filterSpec.d.ts +10 -0
- package/lib/packlets/file-tree/filterSpec.js +77 -0
- package/lib/packlets/file-tree/fsTree.d.ts +37 -13
- package/lib/packlets/file-tree/fsTree.js +106 -11
- package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts +37 -13
- package/lib/packlets/file-tree/in-memory/inMemoryTree.js +278 -20
- package/lib/packlets/file-tree/in-memory/treeBuilder.d.ts +15 -0
- package/lib/packlets/file-tree/in-memory/treeBuilder.js +31 -0
- package/lib/packlets/file-tree/index.browser.d.ts +1 -0
- package/lib/packlets/file-tree/index.browser.js +1 -0
- package/lib/packlets/file-tree/index.d.ts +1 -0
- package/lib/packlets/file-tree/index.js +1 -0
- package/lib/packlets/json-file/file.d.ts +1 -1
- package/lib/packlets/json-file/file.js +1 -1
- package/lib/packlets/json-file/jsonFsHelper.d.ts +1 -1
- package/lib/packlets/json-file/jsonFsHelper.js +1 -1
- package/lib/packlets/validators/validators.d.ts +9 -9
- package/lib/packlets/validators/validators.js +8 -8
- package/package.json +29 -30
- package/dist/test/fixtures/file-tree/docs/api/reference.json +0 -1
|
@@ -30,9 +30,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
30
30
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
31
31
|
const directoryItem_1 = require("./directoryItem");
|
|
32
32
|
const fileItem_1 = require("./fileItem");
|
|
33
|
+
const filterSpec_1 = require("./filterSpec");
|
|
33
34
|
/**
|
|
34
|
-
* Implementation of {@link FileTree.
|
|
35
|
-
* file system to access files and directories.
|
|
35
|
+
* Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses the
|
|
36
|
+
* file system to access and modify files and directories.
|
|
36
37
|
* @public
|
|
37
38
|
*/
|
|
38
39
|
class FsFileTreeAccessors {
|
|
@@ -42,12 +43,14 @@ class FsFileTreeAccessors {
|
|
|
42
43
|
* @public
|
|
43
44
|
*/
|
|
44
45
|
constructor(params) {
|
|
45
|
-
var _a;
|
|
46
|
+
var _a, _b;
|
|
46
47
|
this.prefix = params === null || params === void 0 ? void 0 : params.prefix;
|
|
47
48
|
this._inferContentType = (_a = params === null || params === void 0 ? void 0 : params.inferContentType) !== null && _a !== void 0 ? _a : fileItem_1.FileItem.defaultInferContentType;
|
|
49
|
+
/* c8 ignore next 1 - defensive default when params is undefined */
|
|
50
|
+
this._mutable = (_b = params === null || params === void 0 ? void 0 : params.mutable) !== null && _b !== void 0 ? _b : false;
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
|
-
* {@
|
|
53
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
|
|
51
54
|
*/
|
|
52
55
|
resolveAbsolutePath(...paths) {
|
|
53
56
|
if (this.prefix && !path_1.default.isAbsolute(paths[0])) {
|
|
@@ -56,25 +59,25 @@ class FsFileTreeAccessors {
|
|
|
56
59
|
return path_1.default.resolve(...paths);
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
|
-
* {@
|
|
62
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
|
|
60
63
|
*/
|
|
61
64
|
getExtension(itemPath) {
|
|
62
65
|
return path_1.default.extname(itemPath);
|
|
63
66
|
}
|
|
64
67
|
/**
|
|
65
|
-
* {@
|
|
68
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
|
|
66
69
|
*/
|
|
67
70
|
getBaseName(itemPath, suffix) {
|
|
68
71
|
return path_1.default.basename(itemPath, suffix);
|
|
69
72
|
}
|
|
70
73
|
/**
|
|
71
|
-
* {@
|
|
74
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
|
|
72
75
|
*/
|
|
73
76
|
joinPaths(...paths) {
|
|
74
77
|
return path_1.default.join(...paths);
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* {@
|
|
80
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getItem}
|
|
78
81
|
*/
|
|
79
82
|
getItem(itemPath) {
|
|
80
83
|
return (0, ts_utils_1.captureResult)(() => {
|
|
@@ -90,13 +93,13 @@ class FsFileTreeAccessors {
|
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
|
-
* {@
|
|
96
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
|
|
94
97
|
*/
|
|
95
98
|
getFileContents(filePath) {
|
|
96
99
|
return (0, ts_utils_1.captureResult)(() => fs_1.default.readFileSync(this.resolveAbsolutePath(filePath), 'utf8'));
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
|
-
* {@
|
|
102
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
|
|
100
103
|
*/
|
|
101
104
|
getFileContentType(filePath, provided) {
|
|
102
105
|
if (provided !== undefined) {
|
|
@@ -106,7 +109,7 @@ class FsFileTreeAccessors {
|
|
|
106
109
|
return this._inferContentType(filePath);
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
109
|
-
* {@
|
|
112
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
|
|
110
113
|
*/
|
|
111
114
|
getChildren(dirPath) {
|
|
112
115
|
return (0, ts_utils_1.captureResult)(() => {
|
|
@@ -124,6 +127,98 @@ class FsFileTreeAccessors {
|
|
|
124
127
|
return children;
|
|
125
128
|
});
|
|
126
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
|
|
132
|
+
*/
|
|
133
|
+
fileIsMutable(path) {
|
|
134
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
135
|
+
// Check if mutability is disabled
|
|
136
|
+
if (this._mutable === false) {
|
|
137
|
+
return (0, ts_utils_1.failWithDetail)(`${absolutePath}: mutability is disabled`, 'not-mutable');
|
|
138
|
+
}
|
|
139
|
+
// Check if path is excluded by filter
|
|
140
|
+
if (!(0, filterSpec_1.isPathMutable)(absolutePath, this._mutable)) {
|
|
141
|
+
return (0, ts_utils_1.failWithDetail)(`${absolutePath}: path is excluded by filter`, 'path-excluded');
|
|
142
|
+
}
|
|
143
|
+
// Check file system permissions
|
|
144
|
+
try {
|
|
145
|
+
// Check if file exists
|
|
146
|
+
if (fs_1.default.existsSync(absolutePath)) {
|
|
147
|
+
fs_1.default.accessSync(absolutePath, fs_1.default.constants.W_OK);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Check if parent directory is writable
|
|
151
|
+
const parentDir = absolutePath.substring(0, absolutePath.lastIndexOf('/'));
|
|
152
|
+
if (parentDir && fs_1.default.existsSync(parentDir)) {
|
|
153
|
+
fs_1.default.accessSync(parentDir, fs_1.default.constants.W_OK);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return (0, ts_utils_1.succeedWithDetail)(true, 'persistent');
|
|
157
|
+
/* c8 ignore next 3 - unreachable when running as root (CI), tested in mutableFsTree.test.ts */
|
|
158
|
+
}
|
|
159
|
+
catch (_a) {
|
|
160
|
+
return (0, ts_utils_1.failWithDetail)(`${absolutePath}: permission denied`, 'permission-denied');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
|
|
165
|
+
*/
|
|
166
|
+
saveFileContents(path, contents) {
|
|
167
|
+
return this.fileIsMutable(path).asResult.onSuccess(() => {
|
|
168
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
169
|
+
return (0, ts_utils_1.captureResult)(() => {
|
|
170
|
+
fs_1.default.writeFileSync(absolutePath, contents, 'utf8');
|
|
171
|
+
return contents;
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteFile}
|
|
177
|
+
*/
|
|
178
|
+
deleteFile(path) {
|
|
179
|
+
return this.fileIsMutable(path).asResult.onSuccess(() => {
|
|
180
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
181
|
+
return (0, ts_utils_1.captureResult)(() => {
|
|
182
|
+
const stat = fs_1.default.statSync(absolutePath);
|
|
183
|
+
if (!stat.isFile()) {
|
|
184
|
+
throw new Error(`${absolutePath}: not a file`);
|
|
185
|
+
}
|
|
186
|
+
fs_1.default.unlinkSync(absolutePath);
|
|
187
|
+
return true;
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
|
|
193
|
+
*/
|
|
194
|
+
createDirectory(dirPath) {
|
|
195
|
+
const absolutePath = this.resolveAbsolutePath(dirPath);
|
|
196
|
+
// Check if mutability is disabled
|
|
197
|
+
if (this._mutable === false) {
|
|
198
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: mutability is disabled`);
|
|
199
|
+
}
|
|
200
|
+
return (0, ts_utils_1.captureResult)(() => {
|
|
201
|
+
fs_1.default.mkdirSync(absolutePath, { recursive: true });
|
|
202
|
+
return absolutePath;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteDirectory}
|
|
207
|
+
*/
|
|
208
|
+
deleteDirectory(dirPath) {
|
|
209
|
+
return this.fileIsMutable(dirPath).asResult.onSuccess(() => {
|
|
210
|
+
const absolutePath = this.resolveAbsolutePath(dirPath);
|
|
211
|
+
return (0, ts_utils_1.captureResult)(() => {
|
|
212
|
+
const stat = fs_1.default.statSync(absolutePath);
|
|
213
|
+
if (!stat.isDirectory()) {
|
|
214
|
+
throw new Error(`${absolutePath}: not a directory`);
|
|
215
|
+
}
|
|
216
|
+
// fs.rmdirSync fails if directory is non-empty (desired behavior)
|
|
217
|
+
fs_1.default.rmdirSync(absolutePath);
|
|
218
|
+
return true;
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
127
222
|
}
|
|
128
223
|
exports.FsFileTreeAccessors = FsFileTreeAccessors;
|
|
129
224
|
//# sourceMappingURL=fsTree.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import { FileTreeItem,
|
|
1
|
+
import { DetailedResult, Result } from '@fgv/ts-utils';
|
|
2
|
+
import { FileTreeItem, IFileTreeInitParams, IMutableFileTreeAccessors, SaveDetail } from '../fileTreeAccessors';
|
|
3
3
|
/**
|
|
4
4
|
* Represents a single file in an in-memory {@link FileTree | file tree}.
|
|
5
5
|
* @public
|
|
@@ -19,13 +19,16 @@ export interface IInMemoryFile<TCT extends string = string> {
|
|
|
19
19
|
readonly contentType?: TCT;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Implementation of {@link FileTree.
|
|
23
|
-
* tree to access files and directories.
|
|
22
|
+
* Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses an in-memory
|
|
23
|
+
* tree to access and modify files and directories.
|
|
24
24
|
* @public
|
|
25
25
|
*/
|
|
26
|
-
export declare class InMemoryTreeAccessors<TCT extends string = string> implements
|
|
26
|
+
export declare class InMemoryTreeAccessors<TCT extends string = string> implements IMutableFileTreeAccessors<TCT> {
|
|
27
27
|
private readonly _tree;
|
|
28
28
|
private readonly _inferContentType;
|
|
29
|
+
private readonly _mutable;
|
|
30
|
+
private readonly _mutableByPath;
|
|
31
|
+
private readonly _mutableRoot;
|
|
29
32
|
/**
|
|
30
33
|
* Protected constructor for derived classes.
|
|
31
34
|
* @param files - An array of {@link FileTree.IInMemoryFile | in-memory files} to include in the tree.
|
|
@@ -48,36 +51,57 @@ export declare class InMemoryTreeAccessors<TCT extends string = string> implemen
|
|
|
48
51
|
*/
|
|
49
52
|
static create<TCT extends string = string>(files: IInMemoryFile<TCT>[], params?: IFileTreeInitParams<TCT>): Result<InMemoryTreeAccessors<TCT>>;
|
|
50
53
|
/**
|
|
51
|
-
* {@
|
|
54
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
|
|
52
55
|
*/
|
|
53
56
|
resolveAbsolutePath(...paths: string[]): string;
|
|
54
57
|
/**
|
|
55
|
-
* {@
|
|
58
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
|
|
56
59
|
*/
|
|
57
60
|
getExtension(path: string): string;
|
|
58
61
|
/**
|
|
59
|
-
* {@
|
|
62
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
|
|
60
63
|
*/
|
|
61
64
|
getBaseName(path: string, suffix?: string): string;
|
|
62
65
|
/**
|
|
63
|
-
* {@
|
|
66
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
|
|
64
67
|
*/
|
|
65
68
|
joinPaths(...paths: string[]): string;
|
|
66
69
|
/**
|
|
67
|
-
* {@
|
|
70
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getItem}
|
|
68
71
|
*/
|
|
69
72
|
getItem(itemPath: string): Result<FileTreeItem<TCT>>;
|
|
70
73
|
/**
|
|
71
|
-
* {@
|
|
74
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
|
|
72
75
|
*/
|
|
73
76
|
getFileContents(path: string): Result<string>;
|
|
74
77
|
/**
|
|
75
|
-
* {@
|
|
78
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
|
|
76
79
|
*/
|
|
77
80
|
getFileContentType(path: string, provided?: string): Result<TCT | undefined>;
|
|
78
81
|
/**
|
|
79
|
-
* {@
|
|
82
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
|
|
80
83
|
*/
|
|
81
84
|
getChildren(path: string): Result<ReadonlyArray<FileTreeItem<TCT>>>;
|
|
85
|
+
private _addMutableFile;
|
|
86
|
+
/**
|
|
87
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
|
|
88
|
+
*/
|
|
89
|
+
createDirectory(dirPath: string): Result<string>;
|
|
90
|
+
/**
|
|
91
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
|
|
92
|
+
*/
|
|
93
|
+
fileIsMutable(path: string): DetailedResult<boolean, SaveDetail>;
|
|
94
|
+
/**
|
|
95
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteFile}
|
|
96
|
+
*/
|
|
97
|
+
deleteFile(path: string): Result<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteDirectory}
|
|
100
|
+
*/
|
|
101
|
+
deleteDirectory(path: string): Result<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
|
|
104
|
+
*/
|
|
105
|
+
saveFileContents(path: string, contents: string): Result<string>;
|
|
82
106
|
}
|
|
83
107
|
//# sourceMappingURL=inMemoryTree.d.ts.map
|
|
@@ -26,9 +26,82 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
26
26
|
const directoryItem_1 = require("../directoryItem");
|
|
27
27
|
const fileItem_1 = require("../fileItem");
|
|
28
28
|
const treeBuilder_1 = require("./treeBuilder");
|
|
29
|
+
const filterSpec_1 = require("../filterSpec");
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* A mutable in-memory file that allows updating contents.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
class MutableInMemoryFile {
|
|
35
|
+
constructor(absolutePath, contents, contentType) {
|
|
36
|
+
this.absolutePath = absolutePath;
|
|
37
|
+
this._contents = contents;
|
|
38
|
+
this.contentType = contentType;
|
|
39
|
+
}
|
|
40
|
+
get contents() {
|
|
41
|
+
return this._contents;
|
|
42
|
+
}
|
|
43
|
+
setContents(contents) {
|
|
44
|
+
this._contents = contents;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A mutable in-memory directory that creates mutable files.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
class MutableInMemoryDirectory {
|
|
52
|
+
/* c8 ignore next 3 - internal getter used by tree traversal */
|
|
53
|
+
get children() {
|
|
54
|
+
return this._children;
|
|
55
|
+
}
|
|
56
|
+
constructor(absolutePath) {
|
|
57
|
+
this.absolutePath = absolutePath;
|
|
58
|
+
this._children = new Map();
|
|
59
|
+
}
|
|
60
|
+
getChildPath(name) {
|
|
61
|
+
if (this.absolutePath === '/') {
|
|
62
|
+
return `/${name}`;
|
|
63
|
+
}
|
|
64
|
+
return [this.absolutePath, name].join('/');
|
|
65
|
+
}
|
|
66
|
+
addFile(name, contents, contentType) {
|
|
67
|
+
/* c8 ignore next 3 - defensive: duplicate detection during construction */
|
|
68
|
+
if (this._children.has(name)) {
|
|
69
|
+
return (0, ts_utils_1.fail)(`${name}: already exists`);
|
|
70
|
+
}
|
|
71
|
+
const child = new MutableInMemoryFile(this.getChildPath(name), contents, contentType);
|
|
72
|
+
this._children.set(name, child);
|
|
73
|
+
return (0, ts_utils_1.succeed)(child);
|
|
74
|
+
}
|
|
75
|
+
getOrAddDirectory(name) {
|
|
76
|
+
const existing = this._children.get(name);
|
|
77
|
+
if (existing) {
|
|
78
|
+
if (existing instanceof MutableInMemoryDirectory) {
|
|
79
|
+
return (0, ts_utils_1.succeed)(existing);
|
|
80
|
+
}
|
|
81
|
+
return (0, ts_utils_1.fail)(`${name}: not a directory`);
|
|
82
|
+
}
|
|
83
|
+
const child = new MutableInMemoryDirectory(this.getChildPath(name));
|
|
84
|
+
this._children.set(name, child);
|
|
85
|
+
return (0, ts_utils_1.succeed)(child);
|
|
86
|
+
}
|
|
87
|
+
updateOrAddFile(name, contents, contentType) {
|
|
88
|
+
const existing = this._children.get(name);
|
|
89
|
+
if (existing) {
|
|
90
|
+
if (existing instanceof MutableInMemoryFile) {
|
|
91
|
+
existing.setContents(contents);
|
|
92
|
+
return (0, ts_utils_1.succeed)(existing);
|
|
93
|
+
}
|
|
94
|
+
return (0, ts_utils_1.fail)(`${name}: not a file`);
|
|
95
|
+
}
|
|
96
|
+
return this.addFile(name, contents, contentType);
|
|
97
|
+
}
|
|
98
|
+
removeChild(name) {
|
|
99
|
+
return this._children.delete(name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses an in-memory
|
|
104
|
+
* tree to access and modify files and directories.
|
|
32
105
|
* @public
|
|
33
106
|
*/
|
|
34
107
|
class InMemoryTreeAccessors {
|
|
@@ -39,12 +112,18 @@ class InMemoryTreeAccessors {
|
|
|
39
112
|
* @public
|
|
40
113
|
*/
|
|
41
114
|
constructor(files, params) {
|
|
42
|
-
var _a, _b;
|
|
115
|
+
var _a, _b, _c, _d;
|
|
43
116
|
this._tree = treeBuilder_1.TreeBuilder.create(params === null || params === void 0 ? void 0 : params.prefix).orThrow();
|
|
44
117
|
this._inferContentType = (_a = params === null || params === void 0 ? void 0 : params.inferContentType) !== null && _a !== void 0 ? _a : fileItem_1.FileItem.defaultInferContentType;
|
|
118
|
+
this._mutable = (_b = params === null || params === void 0 ? void 0 : params.mutable) !== null && _b !== void 0 ? _b : false;
|
|
119
|
+
this._mutableByPath = new Map();
|
|
120
|
+
const prefix = (_c = params === null || params === void 0 ? void 0 : params.prefix) !== null && _c !== void 0 ? _c : '/';
|
|
121
|
+
this._mutableRoot = new MutableInMemoryDirectory(prefix.endsWith('/') ? prefix.slice(0, -1) || '/' : prefix);
|
|
122
|
+
this._mutableByPath.set(this._mutableRoot.absolutePath, this._mutableRoot);
|
|
45
123
|
for (const file of files) {
|
|
46
|
-
const contentType = (
|
|
124
|
+
const contentType = (_d = file.contentType) !== null && _d !== void 0 ? _d : this._inferContentType(file.path).orDefault();
|
|
47
125
|
this._tree.addFile(file.path, file.contents, contentType).orThrow();
|
|
126
|
+
this._addMutableFile(file.path, file.contents, contentType);
|
|
48
127
|
}
|
|
49
128
|
}
|
|
50
129
|
/**
|
|
@@ -59,7 +138,7 @@ class InMemoryTreeAccessors {
|
|
|
59
138
|
return (0, ts_utils_1.captureResult)(() => new InMemoryTreeAccessors(files, params));
|
|
60
139
|
}
|
|
61
140
|
/**
|
|
62
|
-
* {@
|
|
141
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
|
|
63
142
|
*/
|
|
64
143
|
resolveAbsolutePath(...paths) {
|
|
65
144
|
const parts = paths[0].startsWith('/') ? paths : [this._tree.prefix, ...paths];
|
|
@@ -67,7 +146,7 @@ class InMemoryTreeAccessors {
|
|
|
67
146
|
return `/${joined}`;
|
|
68
147
|
}
|
|
69
148
|
/**
|
|
70
|
-
* {@
|
|
149
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
|
|
71
150
|
*/
|
|
72
151
|
getExtension(path) {
|
|
73
152
|
const parts = path.split('.');
|
|
@@ -77,7 +156,7 @@ class InMemoryTreeAccessors {
|
|
|
77
156
|
return `.${parts.pop()}`;
|
|
78
157
|
}
|
|
79
158
|
/**
|
|
80
|
-
* {@
|
|
159
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
|
|
81
160
|
*/
|
|
82
161
|
getBaseName(path, suffix) {
|
|
83
162
|
var _a;
|
|
@@ -89,13 +168,15 @@ class InMemoryTreeAccessors {
|
|
|
89
168
|
return base;
|
|
90
169
|
}
|
|
91
170
|
/**
|
|
92
|
-
* {@
|
|
171
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
|
|
93
172
|
*/
|
|
94
173
|
joinPaths(...paths) {
|
|
95
|
-
|
|
174
|
+
var _a;
|
|
175
|
+
const joined = paths.flatMap((p) => p.split('/').filter((s) => s.length > 0)).join('/');
|
|
176
|
+
return ((_a = paths[0]) === null || _a === void 0 ? void 0 : _a.startsWith('/')) ? `/${joined}` : joined;
|
|
96
177
|
}
|
|
97
178
|
/**
|
|
98
|
-
* {@
|
|
179
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getItem}
|
|
99
180
|
*/
|
|
100
181
|
getItem(itemPath) {
|
|
101
182
|
const existing = this._tree.byAbsolutePath.get(itemPath);
|
|
@@ -110,26 +191,24 @@ class InMemoryTreeAccessors {
|
|
|
110
191
|
return (0, ts_utils_1.fail)(`${itemPath}: not found`);
|
|
111
192
|
}
|
|
112
193
|
/**
|
|
113
|
-
* {@
|
|
194
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
|
|
114
195
|
*/
|
|
115
196
|
getFileContents(path) {
|
|
116
|
-
const
|
|
197
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
198
|
+
const item = this._mutableByPath.get(absolutePath);
|
|
117
199
|
if (item === undefined) {
|
|
118
|
-
return (0, ts_utils_1.fail)(`${
|
|
200
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: not found`);
|
|
119
201
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return (0, ts_utils_1.fail)(`${path}: not a file`);
|
|
202
|
+
if (!(item instanceof MutableInMemoryFile)) {
|
|
203
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: not a file`);
|
|
123
204
|
}
|
|
124
|
-
// if the body is a string we don't want to add quotes
|
|
125
205
|
if (typeof item.contents === 'string') {
|
|
126
206
|
return (0, ts_utils_1.succeed)(item.contents);
|
|
127
207
|
}
|
|
128
|
-
/* c8 ignore next 2 - local coverage is 100% but build coverage has intermittent issues */
|
|
129
208
|
return (0, ts_utils_1.captureResult)(() => JSON.stringify(item.contents));
|
|
130
209
|
}
|
|
131
210
|
/**
|
|
132
|
-
* {@
|
|
211
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
|
|
133
212
|
*/
|
|
134
213
|
getFileContentType(path, provided) {
|
|
135
214
|
// If provided contentType is given, use it directly (highest priority)
|
|
@@ -152,7 +231,7 @@ class InMemoryTreeAccessors {
|
|
|
152
231
|
return this._inferContentType(path);
|
|
153
232
|
}
|
|
154
233
|
/**
|
|
155
|
-
* {@
|
|
234
|
+
* {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
|
|
156
235
|
*/
|
|
157
236
|
getChildren(path) {
|
|
158
237
|
const item = this._tree.byAbsolutePath.get(path);
|
|
@@ -176,6 +255,185 @@ class InMemoryTreeAccessors {
|
|
|
176
255
|
return children;
|
|
177
256
|
});
|
|
178
257
|
}
|
|
258
|
+
_addMutableFile(path, contents, contentType) {
|
|
259
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
260
|
+
const parts = absolutePath.split('/').filter((p) => p.length > 0);
|
|
261
|
+
/* c8 ignore next 3 - defensive: invalid path detection */
|
|
262
|
+
if (parts.length === 0) {
|
|
263
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: invalid file path`);
|
|
264
|
+
}
|
|
265
|
+
let dir = this._mutableRoot;
|
|
266
|
+
while (parts.length > 1) {
|
|
267
|
+
const part = parts.shift();
|
|
268
|
+
const result = dir.getOrAddDirectory(part);
|
|
269
|
+
/* c8 ignore next 3 - defensive: directory conflict during construction */
|
|
270
|
+
if (result.isFailure()) {
|
|
271
|
+
return (0, ts_utils_1.fail)(result.message);
|
|
272
|
+
}
|
|
273
|
+
dir = result.value;
|
|
274
|
+
if (!this._mutableByPath.has(dir.absolutePath)) {
|
|
275
|
+
this._mutableByPath.set(dir.absolutePath, dir);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return dir.addFile(parts[0], contents, contentType).onSuccess((file) => {
|
|
279
|
+
this._mutableByPath.set(file.absolutePath, file);
|
|
280
|
+
return (0, ts_utils_1.succeed)(file);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
|
|
285
|
+
*/
|
|
286
|
+
createDirectory(dirPath) {
|
|
287
|
+
const absolutePath = this.resolveAbsolutePath(dirPath);
|
|
288
|
+
// Check if mutability is disabled
|
|
289
|
+
if (this._mutable === false) {
|
|
290
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: mutability is disabled`);
|
|
291
|
+
}
|
|
292
|
+
// Add to the TreeBuilder (read layer)
|
|
293
|
+
const treeResult = this._tree.addDirectory(absolutePath);
|
|
294
|
+
/* c8 ignore next 3 - defensive: read layer failure would indicate internal inconsistency */
|
|
295
|
+
if (treeResult.isFailure()) {
|
|
296
|
+
return (0, ts_utils_1.fail)(treeResult.message);
|
|
297
|
+
}
|
|
298
|
+
// Add to the mutable layer
|
|
299
|
+
const parts = absolutePath.split('/').filter((p) => p.length > 0);
|
|
300
|
+
let dir = this._mutableRoot;
|
|
301
|
+
for (const part of parts) {
|
|
302
|
+
const result = dir.getOrAddDirectory(part);
|
|
303
|
+
/* c8 ignore next 3 - defensive: mutable layer should match read layer state */
|
|
304
|
+
if (result.isFailure()) {
|
|
305
|
+
return (0, ts_utils_1.fail)(result.message);
|
|
306
|
+
}
|
|
307
|
+
dir = result.value;
|
|
308
|
+
if (!this._mutableByPath.has(dir.absolutePath)) {
|
|
309
|
+
this._mutableByPath.set(dir.absolutePath, dir);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return (0, ts_utils_1.succeed)(absolutePath);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
|
|
316
|
+
*/
|
|
317
|
+
fileIsMutable(path) {
|
|
318
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
319
|
+
// Check if mutability is disabled
|
|
320
|
+
if (this._mutable === false) {
|
|
321
|
+
return (0, ts_utils_1.failWithDetail)(`${absolutePath}: mutability is disabled`, 'not-mutable');
|
|
322
|
+
}
|
|
323
|
+
// Check if path is excluded by filter
|
|
324
|
+
if (!(0, filterSpec_1.isPathMutable)(absolutePath, this._mutable)) {
|
|
325
|
+
return (0, ts_utils_1.failWithDetail)(`${absolutePath}: path is excluded by filter`, 'path-excluded');
|
|
326
|
+
}
|
|
327
|
+
return (0, ts_utils_1.succeedWithDetail)(true, 'transient');
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteFile}
|
|
331
|
+
*/
|
|
332
|
+
deleteFile(path) {
|
|
333
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
334
|
+
const parts = absolutePath.split('/').filter((p) => p.length > 0);
|
|
335
|
+
if (parts.length === 0) {
|
|
336
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: invalid file path`);
|
|
337
|
+
}
|
|
338
|
+
const fileName = parts.pop();
|
|
339
|
+
// Navigate to parent directory
|
|
340
|
+
let dir = this._mutableRoot;
|
|
341
|
+
for (const part of parts) {
|
|
342
|
+
const child = dir.children.get(part);
|
|
343
|
+
if (!child || !(child instanceof MutableInMemoryDirectory)) {
|
|
344
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: parent directory not found`);
|
|
345
|
+
}
|
|
346
|
+
dir = child;
|
|
347
|
+
}
|
|
348
|
+
if (!dir.removeChild(fileName)) {
|
|
349
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: file not found`);
|
|
350
|
+
}
|
|
351
|
+
// Also remove from the read layer's directory children and path index
|
|
352
|
+
const parentPath = parts.length === 0 ? '/' : '/' + parts.join('/');
|
|
353
|
+
const readParent = this._tree.byAbsolutePath.get(parentPath);
|
|
354
|
+
if (readParent instanceof treeBuilder_1.InMemoryDirectory) {
|
|
355
|
+
readParent.removeChild(fileName);
|
|
356
|
+
}
|
|
357
|
+
this._tree.byAbsolutePath.delete(absolutePath);
|
|
358
|
+
this._mutableByPath.delete(absolutePath);
|
|
359
|
+
return (0, ts_utils_1.succeed)(true);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.deleteDirectory}
|
|
363
|
+
*/
|
|
364
|
+
deleteDirectory(path) {
|
|
365
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
366
|
+
const parts = absolutePath.split('/').filter((p) => p.length > 0);
|
|
367
|
+
if (parts.length === 0) {
|
|
368
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: invalid directory path`);
|
|
369
|
+
}
|
|
370
|
+
const dirName = parts.pop();
|
|
371
|
+
// Navigate to parent directory
|
|
372
|
+
let parentDir = this._mutableRoot;
|
|
373
|
+
for (const part of parts) {
|
|
374
|
+
const child = parentDir.children.get(part);
|
|
375
|
+
if (!child || !(child instanceof MutableInMemoryDirectory)) {
|
|
376
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: parent directory not found`);
|
|
377
|
+
}
|
|
378
|
+
parentDir = child;
|
|
379
|
+
}
|
|
380
|
+
// Verify target is a directory
|
|
381
|
+
const target = parentDir.children.get(dirName);
|
|
382
|
+
if (!target || !(target instanceof MutableInMemoryDirectory)) {
|
|
383
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: not a directory`);
|
|
384
|
+
}
|
|
385
|
+
// Check non-empty
|
|
386
|
+
if (target.children.size > 0) {
|
|
387
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: directory is not empty`);
|
|
388
|
+
}
|
|
389
|
+
parentDir.removeChild(dirName);
|
|
390
|
+
// Also remove from the read layer
|
|
391
|
+
/* c8 ignore next 1 - defensive: branch for top-level directory deletion */
|
|
392
|
+
const readParentPath = parts.length === 0 ? '/' : '/' + parts.join('/');
|
|
393
|
+
const readParent = this._tree.byAbsolutePath.get(readParentPath);
|
|
394
|
+
if (readParent instanceof treeBuilder_1.InMemoryDirectory) {
|
|
395
|
+
readParent.removeChild(dirName);
|
|
396
|
+
}
|
|
397
|
+
this._tree.byAbsolutePath.delete(absolutePath);
|
|
398
|
+
this._mutableByPath.delete(absolutePath);
|
|
399
|
+
return (0, ts_utils_1.succeed)(true);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
|
|
403
|
+
*/
|
|
404
|
+
saveFileContents(path, contents) {
|
|
405
|
+
const isMutable = this.fileIsMutable(path);
|
|
406
|
+
if (isMutable.isFailure()) {
|
|
407
|
+
return (0, ts_utils_1.fail)(isMutable.message);
|
|
408
|
+
}
|
|
409
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
410
|
+
const parts = absolutePath.split('/').filter((p) => p.length > 0);
|
|
411
|
+
if (parts.length === 0) {
|
|
412
|
+
return (0, ts_utils_1.fail)(`${absolutePath}: invalid file path`);
|
|
413
|
+
}
|
|
414
|
+
// Navigate to parent directory, creating directories as needed
|
|
415
|
+
let dir = this._mutableRoot;
|
|
416
|
+
while (parts.length > 1) {
|
|
417
|
+
const part = parts.shift();
|
|
418
|
+
const result = dir.getOrAddDirectory(part);
|
|
419
|
+
if (result.isFailure()) {
|
|
420
|
+
return (0, ts_utils_1.fail)(result.message);
|
|
421
|
+
}
|
|
422
|
+
dir = result.value;
|
|
423
|
+
if (!this._mutableByPath.has(dir.absolutePath)) {
|
|
424
|
+
this._mutableByPath.set(dir.absolutePath, dir);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Update or add the file in the mutable layer
|
|
428
|
+
return dir.updateOrAddFile(parts[0], contents).onSuccess((file) => {
|
|
429
|
+
this._mutableByPath.set(file.absolutePath, file);
|
|
430
|
+
// Also register in the read layer so getItem/getChildren can find it
|
|
431
|
+
if (!this._tree.byAbsolutePath.has(file.absolutePath)) {
|
|
432
|
+
this._tree.addFile(file.absolutePath, contents);
|
|
433
|
+
}
|
|
434
|
+
return (0, ts_utils_1.succeed)(contents);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
179
437
|
}
|
|
180
438
|
exports.InMemoryTreeAccessors = InMemoryTreeAccessors;
|
|
181
439
|
//# sourceMappingURL=inMemoryTree.js.map
|
|
@@ -59,6 +59,12 @@ export declare class InMemoryDirectory<TCT extends string = string> {
|
|
|
59
59
|
* `Failure` with an error message otherwise.
|
|
60
60
|
*/
|
|
61
61
|
addFile(name: string, contents: unknown, contentType?: TCT): Result<InMemoryFile<TCT>>;
|
|
62
|
+
/**
|
|
63
|
+
* Removes a child from the directory.
|
|
64
|
+
* @param name - The name of the child to remove.
|
|
65
|
+
* @returns `true` if the child was found and removed, `false` otherwise.
|
|
66
|
+
*/
|
|
67
|
+
removeChild(name: string): boolean;
|
|
62
68
|
/**
|
|
63
69
|
* Gets the absolute path for a child of this directory with the supplied
|
|
64
70
|
* name.
|
|
@@ -109,5 +115,14 @@ export declare class TreeBuilder<TCT extends string = string> {
|
|
|
109
115
|
* @public
|
|
110
116
|
*/
|
|
111
117
|
addFile(absolutePath: string, contents: unknown, contentType?: TCT): Result<InMemoryFile<TCT>>;
|
|
118
|
+
/**
|
|
119
|
+
* Ensures a directory exists at the given absolute path, creating
|
|
120
|
+
* intermediate directories as needed.
|
|
121
|
+
* @param absolutePath - The absolute path of the directory.
|
|
122
|
+
* @returns `Success` with the directory if successful, or
|
|
123
|
+
* `Failure` with an error message otherwise.
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
addDirectory(absolutePath: string): Result<InMemoryDirectory<TCT>>;
|
|
112
127
|
}
|
|
113
128
|
//# sourceMappingURL=treeBuilder.d.ts.map
|