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