@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.
Files changed (43) hide show
  1. package/dist/packlets/converters/converters.js +36 -14
  2. package/dist/packlets/file-tree/directoryItem.js +99 -4
  3. package/dist/packlets/file-tree/fileItem.js +47 -9
  4. package/dist/packlets/file-tree/fileTreeAccessors.js +59 -1
  5. package/dist/packlets/file-tree/filterSpec.js +74 -0
  6. package/dist/packlets/file-tree/fsTree.js +107 -12
  7. package/dist/packlets/file-tree/in-memory/inMemoryTree.js +279 -21
  8. package/dist/packlets/file-tree/in-memory/treeBuilder.js +31 -0
  9. package/dist/packlets/file-tree/index.browser.js +1 -0
  10. package/dist/packlets/file-tree/index.js +1 -0
  11. package/dist/packlets/json-file/file.js +1 -1
  12. package/dist/packlets/json-file/jsonFsHelper.js +1 -1
  13. package/dist/packlets/validators/validators.js +8 -8
  14. package/dist/ts-json-base.d.ts +439 -65
  15. package/dist/tsdoc-metadata.json +1 -1
  16. package/lib/packlets/converters/converters.d.ts +20 -13
  17. package/lib/packlets/converters/converters.js +36 -13
  18. package/lib/packlets/file-tree/directoryItem.d.ts +29 -6
  19. package/lib/packlets/file-tree/directoryItem.js +98 -3
  20. package/lib/packlets/file-tree/fileItem.d.ts +31 -14
  21. package/lib/packlets/file-tree/fileItem.js +46 -8
  22. package/lib/packlets/file-tree/fileTreeAccessors.d.ts +237 -3
  23. package/lib/packlets/file-tree/fileTreeAccessors.js +63 -0
  24. package/lib/packlets/file-tree/filterSpec.d.ts +10 -0
  25. package/lib/packlets/file-tree/filterSpec.js +77 -0
  26. package/lib/packlets/file-tree/fsTree.d.ts +37 -13
  27. package/lib/packlets/file-tree/fsTree.js +106 -11
  28. package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts +37 -13
  29. package/lib/packlets/file-tree/in-memory/inMemoryTree.js +278 -20
  30. package/lib/packlets/file-tree/in-memory/treeBuilder.d.ts +15 -0
  31. package/lib/packlets/file-tree/in-memory/treeBuilder.js +31 -0
  32. package/lib/packlets/file-tree/index.browser.d.ts +1 -0
  33. package/lib/packlets/file-tree/index.browser.js +1 -0
  34. package/lib/packlets/file-tree/index.d.ts +1 -0
  35. package/lib/packlets/file-tree/index.js +1 -0
  36. package/lib/packlets/json-file/file.d.ts +1 -1
  37. package/lib/packlets/json-file/file.js +1 -1
  38. package/lib/packlets/json-file/jsonFsHelper.d.ts +1 -1
  39. package/lib/packlets/json-file/jsonFsHelper.js +1 -1
  40. package/lib/packlets/validators/validators.d.ts +9 -9
  41. package/lib/packlets/validators/validators.js +8 -8
  42. package/package.json +29 -30
  43. 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.IFileTreeAccessors} that uses the
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
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
62
+ * {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
60
63
  */
61
64
  getExtension(itemPath) {
62
65
  return path_1.default.extname(itemPath);
63
66
  }
64
67
  /**
65
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
74
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
72
75
  */
73
76
  joinPaths(...paths) {
74
77
  return path_1.default.join(...paths);
75
78
  }
76
79
  /**
77
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
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, IFileTreeAccessors, IFileTreeInitParams } from '../fileTreeAccessors';
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.IFileTreeAccessors} that uses an in-memory
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 IFileTreeAccessors<TCT> {
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
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
54
+ * {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
52
55
  */
53
56
  resolveAbsolutePath(...paths: string[]): string;
54
57
  /**
55
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
58
+ * {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
56
59
  */
57
60
  getExtension(path: string): string;
58
61
  /**
59
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
62
+ * {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
60
63
  */
61
64
  getBaseName(path: string, suffix?: string): string;
62
65
  /**
63
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
66
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
64
67
  */
65
68
  joinPaths(...paths: string[]): string;
66
69
  /**
67
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
70
+ * {@inheritDoc FileTree.IFileTreeAccessors.getItem}
68
71
  */
69
72
  getItem(itemPath: string): Result<FileTreeItem<TCT>>;
70
73
  /**
71
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
74
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
72
75
  */
73
76
  getFileContents(path: string): Result<string>;
74
77
  /**
75
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
78
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
76
79
  */
77
80
  getFileContentType(path: string, provided?: string): Result<TCT | undefined>;
78
81
  /**
79
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
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
- * Implementation of {@link FileTree.IFileTreeAccessors} that uses an in-memory
31
- * tree to access files and directories.
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 = (_b = file.contentType) !== null && _b !== void 0 ? _b : this._inferContentType(file.path).orDefault();
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
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
171
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
93
172
  */
94
173
  joinPaths(...paths) {
95
- return paths.join('/');
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
194
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
114
195
  */
115
196
  getFileContents(path) {
116
- const item = this._tree.byAbsolutePath.get(path);
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)(`${path}: not found`);
200
+ return (0, ts_utils_1.fail)(`${absolutePath}: not found`);
119
201
  }
120
- /* c8 ignore next 3 - local coverage is 100% but build coverage has intermittent issues */
121
- if (!(item instanceof treeBuilder_1.InMemoryFile)) {
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
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
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
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