@fgv/ts-json-base 5.0.2 → 5.1.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/converters/converters.js +36 -14
- package/dist/packlets/file-tree/directoryItem.js +35 -4
- package/dist/packlets/file-tree/fileItem.js +37 -9
- package/dist/packlets/file-tree/fileTreeAccessors.js +24 -1
- package/dist/packlets/file-tree/filterSpec.js +74 -0
- package/dist/packlets/file-tree/fsTree.js +73 -12
- package/dist/packlets/file-tree/in-memory/inMemoryTree.js +204 -21
- package/dist/packlets/file-tree/in-memory/treeBuilder.js +23 -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 +290 -61
- 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 +13 -5
- package/lib/packlets/file-tree/directoryItem.js +34 -3
- package/lib/packlets/file-tree/fileItem.d.ts +26 -13
- package/lib/packlets/file-tree/fileItem.js +36 -8
- package/lib/packlets/file-tree/fileTreeAccessors.d.ts +141 -1
- package/lib/packlets/file-tree/fileTreeAccessors.js +26 -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 +29 -13
- package/lib/packlets/file-tree/fsTree.js +72 -11
- package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts +29 -13
- package/lib/packlets/file-tree/in-memory/inMemoryTree.js +203 -20
- package/lib/packlets/file-tree/in-memory/treeBuilder.d.ts +9 -0
- package/lib/packlets/file-tree/in-memory/treeBuilder.js +23 -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 +18 -18
|
@@ -19,7 +19,7 @@
|
|
|
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 { Conversion, Converters as BaseConverters, StringConverter, fail, succeed } from '@fgv/ts-utils';
|
|
22
|
+
import { Conversion, Converters as BaseConverters, StringConverter, captureResult, fail, succeed } from '@fgv/ts-utils';
|
|
23
23
|
import { isJsonArray, isJsonObject } from '../json';
|
|
24
24
|
/**
|
|
25
25
|
* An converter which converts a supplied `unknown` value to a valid {@link JsonPrimitive | JsonPrimitive}.
|
|
@@ -45,7 +45,7 @@ export const jsonPrimitive = new Conversion.BaseConverter((from, __self, ctx) =>
|
|
|
45
45
|
* An copying converter which converts a supplied `unknown` value into
|
|
46
46
|
* a valid {@link JsonObject | JsonObject}. Fails by default if any properties or array elements
|
|
47
47
|
* are `undefined` - this default behavior can be overridden by supplying an appropriate
|
|
48
|
-
*
|
|
48
|
+
* `IJsonConverterContext` at runtime.
|
|
49
49
|
*
|
|
50
50
|
* Guaranteed to return a new object.
|
|
51
51
|
* @public
|
|
@@ -81,7 +81,7 @@ export const jsonObject = new Conversion.BaseConverter((from, __self, ctx) => {
|
|
|
81
81
|
* An copying converter which converts a supplied `unknown` value to
|
|
82
82
|
* a valid {@link JsonArray | JsonArray}. Fails by default if any properties or array elements
|
|
83
83
|
* are `undefined` - this default behavior can be overridden by supplying an appropriate
|
|
84
|
-
*
|
|
84
|
+
* `IJsonConverterContext` at runtime.
|
|
85
85
|
*
|
|
86
86
|
* Guaranteed to return a new array.
|
|
87
87
|
* @public
|
|
@@ -119,7 +119,7 @@ export const jsonArray = new Conversion.BaseConverter((from, __self, ctx) => {
|
|
|
119
119
|
* An copying converter which converts a supplied `unknown` value to a
|
|
120
120
|
* valid {@link JsonValue | JsonValue}. Fails by default if any properties or array elements
|
|
121
121
|
* are `undefined` - this default behavior can be overridden by supplying an appropriate
|
|
122
|
-
*
|
|
122
|
+
* `IJsonConverterContext` at runtime.
|
|
123
123
|
* @public
|
|
124
124
|
*/
|
|
125
125
|
export const jsonValue = new Conversion.BaseConverter((from, __self, ctx) => {
|
|
@@ -132,28 +132,28 @@ export const jsonValue = new Conversion.BaseConverter((from, __self, ctx) => {
|
|
|
132
132
|
return jsonPrimitive.convert(from, ctx);
|
|
133
133
|
});
|
|
134
134
|
/**
|
|
135
|
-
* A
|
|
136
|
-
* Accepts
|
|
135
|
+
* A `StringConverter` which converts `unknown` to a `string`.
|
|
136
|
+
* Accepts `IJsonConverterContext` but ignores it.
|
|
137
137
|
* @public
|
|
138
138
|
*/
|
|
139
139
|
export const string = new StringConverter();
|
|
140
140
|
/**
|
|
141
|
-
* A
|
|
142
|
-
* Accepts
|
|
141
|
+
* A `Converter` which converts `unknown` to a `number`.
|
|
142
|
+
* Accepts `IJsonConverterContext` but ignores it.
|
|
143
143
|
* Mirrors the behavior of `@fgv/ts-utils`.
|
|
144
144
|
* @public
|
|
145
145
|
*/
|
|
146
146
|
export const number = new Conversion.BaseConverter((from) => BaseConverters.number.convert(from));
|
|
147
147
|
/**
|
|
148
|
-
* A
|
|
149
|
-
* Accepts
|
|
148
|
+
* A `Converter` which converts `unknown` to a `boolean`.
|
|
149
|
+
* Accepts `IJsonConverterContext` but ignores it.
|
|
150
150
|
* Mirrors the behavior of `@fgv/ts-utils`.
|
|
151
151
|
* @public
|
|
152
152
|
*/
|
|
153
153
|
export const boolean = new Conversion.BaseConverter((from) => BaseConverters.boolean.convert(from));
|
|
154
154
|
/**
|
|
155
155
|
* Helper to create a converter for a literal value.
|
|
156
|
-
* Accepts
|
|
156
|
+
* Accepts `IJsonConverterContext` but ignores it.
|
|
157
157
|
* Mirrors the behavior of `@fgv/ts-utils`.
|
|
158
158
|
* @public
|
|
159
159
|
*/
|
|
@@ -161,17 +161,17 @@ export function literal(value) {
|
|
|
161
161
|
return BaseConverters.literal(value);
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* Helper function to create a
|
|
164
|
+
* Helper function to create a `Converter` which converts `unknown` to one of a set of
|
|
165
165
|
* supplied enumerated values. Anything else fails.
|
|
166
166
|
*
|
|
167
167
|
* @remarks
|
|
168
|
-
* This JSON variant accepts an
|
|
168
|
+
* This JSON variant accepts an `IJsonConverterContext` OR
|
|
169
169
|
* a `ReadonlyArray<T>` as its conversion context. If the context is an array, it is used to override the
|
|
170
170
|
* allowed values for that conversion; otherwise, the original `values` supplied at creation time are used.
|
|
171
171
|
*
|
|
172
172
|
* @param values - Array of allowed values.
|
|
173
173
|
* @param message - Optional custom failure message.
|
|
174
|
-
* @returns A new
|
|
174
|
+
* @returns A new `Converter` returning `<T>`.
|
|
175
175
|
* @public
|
|
176
176
|
*/
|
|
177
177
|
export function enumeratedValue(values, message) {
|
|
@@ -184,4 +184,26 @@ export function enumeratedValue(values, message) {
|
|
|
184
184
|
return fail(message !== null && message !== void 0 ? message : `Invalid enumerated value ${JSON.stringify(from)}`);
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Creates a converter that parses JSON string content and then applies the supplied converter.
|
|
189
|
+
* @param converter - Converter to apply to the parsed JSON
|
|
190
|
+
* @returns Converter that parses JSON then validates
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
export function jsonConverter(converter) {
|
|
194
|
+
return new Conversion.BaseConverter((from) => {
|
|
195
|
+
if (typeof from !== 'string') {
|
|
196
|
+
return fail('Input must be a string');
|
|
197
|
+
}
|
|
198
|
+
const parseResult = captureResult(() => JSON.parse(from));
|
|
199
|
+
if (parseResult.isFailure()) {
|
|
200
|
+
return fail(`Failed to parse JSON: ${parseResult.message}`);
|
|
201
|
+
}
|
|
202
|
+
const parsed = parseResult.value;
|
|
203
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
204
|
+
return fail('Failed to parse JSON: JSON content must be an object');
|
|
205
|
+
}
|
|
206
|
+
return converter.convert(parsed);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
187
209
|
//# sourceMappingURL=converters.js.map
|
|
@@ -19,14 +19,15 @@
|
|
|
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 } from '@fgv/ts-utils';
|
|
22
|
+
import { captureResult, fail, succeed } from '@fgv/ts-utils';
|
|
23
|
+
import { isMutableAccessors } from './fileTreeAccessors';
|
|
23
24
|
/**
|
|
24
25
|
* Class representing a directory in a file tree.
|
|
25
26
|
* @public
|
|
26
27
|
*/
|
|
27
28
|
export class DirectoryItem {
|
|
28
29
|
/**
|
|
29
|
-
* {@
|
|
30
|
+
* {@inheritDoc FileTree.IFileTreeDirectoryItem.name}
|
|
30
31
|
*/
|
|
31
32
|
get name() {
|
|
32
33
|
return this._hal.getBaseName(this.absolutePath);
|
|
@@ -40,7 +41,7 @@ export class DirectoryItem {
|
|
|
40
41
|
*/
|
|
41
42
|
constructor(path, hal) {
|
|
42
43
|
/**
|
|
43
|
-
* {@
|
|
44
|
+
* {@inheritDoc FileTree.IFileTreeDirectoryItem."type"}
|
|
44
45
|
*/
|
|
45
46
|
this.type = 'directory';
|
|
46
47
|
this._hal = hal;
|
|
@@ -58,10 +59,40 @@ export class DirectoryItem {
|
|
|
58
59
|
return captureResult(() => new DirectoryItem(path, hal));
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
|
-
* {@
|
|
62
|
+
* {@inheritDoc FileTree.IFileTreeDirectoryItem.getChildren}
|
|
62
63
|
*/
|
|
63
64
|
getChildren() {
|
|
64
65
|
return this._hal.getChildren(this.absolutePath);
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* {@inheritDoc FileTree.IFileTreeDirectoryItem.createChildFile}
|
|
69
|
+
*/
|
|
70
|
+
createChildFile(name, contents) {
|
|
71
|
+
if (!isMutableAccessors(this._hal)) {
|
|
72
|
+
return fail(`${this.absolutePath}: mutation not supported`);
|
|
73
|
+
}
|
|
74
|
+
const filePath = this._hal.joinPaths(this.absolutePath, name);
|
|
75
|
+
return this._hal.saveFileContents(filePath, contents).onSuccess(() => this._hal.getItem(filePath).onSuccess((item) => {
|
|
76
|
+
/* c8 ignore next 3 - defensive: verifies accessor returned correct item type after save */
|
|
77
|
+
if (item.type !== 'file') {
|
|
78
|
+
return fail(`${filePath}: expected file but got ${item.type}`);
|
|
79
|
+
}
|
|
80
|
+
return succeed(item);
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* {@inheritDoc FileTree.IFileTreeDirectoryItem.createChildDirectory}
|
|
85
|
+
*/
|
|
86
|
+
createChildDirectory(name) {
|
|
87
|
+
if (!isMutableAccessors(this._hal)) {
|
|
88
|
+
return fail(`${this.absolutePath}: mutation not supported`);
|
|
89
|
+
}
|
|
90
|
+
/* c8 ignore next 3 - defensive: createDirectory should always exist if isMutableAccessors is true */
|
|
91
|
+
if (this._hal.createDirectory === undefined) {
|
|
92
|
+
return fail(`${this.absolutePath}: directory creation not supported`);
|
|
93
|
+
}
|
|
94
|
+
const dirPath = this._hal.joinPaths(this.absolutePath, name);
|
|
95
|
+
return this._hal.createDirectory(dirPath).onSuccess(() => DirectoryItem.create(dirPath, this._hal));
|
|
96
|
+
}
|
|
66
97
|
}
|
|
67
98
|
//# sourceMappingURL=directoryItem.js.map
|
|
@@ -19,32 +19,33 @@
|
|
|
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, succeed } from '@fgv/ts-utils';
|
|
22
|
+
import { captureResult, fail, failWithDetail, succeed, Success } from '@fgv/ts-utils';
|
|
23
|
+
import { isMutableAccessors } from './fileTreeAccessors';
|
|
23
24
|
/**
|
|
24
25
|
* Class representing a file in a file tree.
|
|
25
26
|
* @public
|
|
26
27
|
*/
|
|
27
28
|
export class FileItem {
|
|
28
29
|
/**
|
|
29
|
-
* {@
|
|
30
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.name}
|
|
30
31
|
*/
|
|
31
32
|
get name() {
|
|
32
33
|
return this._hal.getBaseName(this.absolutePath);
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
|
-
* {@
|
|
36
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.baseName}
|
|
36
37
|
*/
|
|
37
38
|
get baseName() {
|
|
38
39
|
return this._hal.getBaseName(this.absolutePath, this.extension);
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
|
-
* {@
|
|
42
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.extension}
|
|
42
43
|
*/
|
|
43
44
|
get extension() {
|
|
44
45
|
return this._hal.getExtension(this.absolutePath);
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
|
-
* {@
|
|
48
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.contentType}
|
|
48
49
|
*/
|
|
49
50
|
get contentType() {
|
|
50
51
|
return this._contentType;
|
|
@@ -58,7 +59,7 @@ export class FileItem {
|
|
|
58
59
|
*/
|
|
59
60
|
constructor(path, hal) {
|
|
60
61
|
/**
|
|
61
|
-
* {@
|
|
62
|
+
* {@inheritDoc FileTree.IFileTreeFileItem."type"}
|
|
62
63
|
*/
|
|
63
64
|
this.type = 'file';
|
|
64
65
|
this._hal = hal;
|
|
@@ -75,6 +76,16 @@ export class FileItem {
|
|
|
75
76
|
static create(path, hal) {
|
|
76
77
|
return captureResult(() => new FileItem(path, hal));
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.getIsMutable}
|
|
81
|
+
*/
|
|
82
|
+
getIsMutable() {
|
|
83
|
+
if (isMutableAccessors(this._hal)) {
|
|
84
|
+
return this._hal.fileIsMutable(this.absolutePath);
|
|
85
|
+
}
|
|
86
|
+
/* c8 ignore next 2 - defensive: all current accessor implementations support mutation interface */
|
|
87
|
+
return failWithDetail(`${this.absolutePath}: mutation not supported`, 'not-supported');
|
|
88
|
+
}
|
|
78
89
|
getContents(converter) {
|
|
79
90
|
return this._hal
|
|
80
91
|
.getFileContents(this.absolutePath)
|
|
@@ -87,7 +98,7 @@ export class FileItem {
|
|
|
87
98
|
});
|
|
88
99
|
}
|
|
89
100
|
/**
|
|
90
|
-
* {@
|
|
101
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.getRawContents}
|
|
91
102
|
*/
|
|
92
103
|
getRawContents() {
|
|
93
104
|
return this._hal.getFileContents(this.absolutePath);
|
|
@@ -99,15 +110,32 @@ export class FileItem {
|
|
|
99
110
|
setContentType(contentType) {
|
|
100
111
|
this._contentType = contentType;
|
|
101
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.setContents}
|
|
115
|
+
*/
|
|
116
|
+
setContents(json) {
|
|
117
|
+
return captureResult(() => JSON.stringify(json, null, 2)).onSuccess((contents) => this.setRawContents(contents).onSuccess(() => Success.with(json)));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* {@inheritDoc FileTree.IFileTreeFileItem.setRawContents}
|
|
121
|
+
*/
|
|
122
|
+
setRawContents(contents) {
|
|
123
|
+
if (isMutableAccessors(this._hal)) {
|
|
124
|
+
return this._hal.saveFileContents(this.absolutePath, contents);
|
|
125
|
+
}
|
|
126
|
+
/* c8 ignore next 2 - defensive: all current accessor implementations support mutation interface */
|
|
127
|
+
return fail(`${this.absolutePath}: mutation not supported`);
|
|
128
|
+
}
|
|
102
129
|
/**
|
|
103
130
|
* Default function to infer the content type of a file.
|
|
104
131
|
* @param filePath - The path of the file.
|
|
132
|
+
* @param provided - Optional supplied content type.
|
|
105
133
|
* @returns `Success` with the content type of the file if successful, or
|
|
106
134
|
* `Failure` with an error message otherwise.
|
|
107
135
|
* @remarks This default implementation always returns `Success` with `undefined`.
|
|
108
136
|
* @public
|
|
109
137
|
*/
|
|
110
|
-
static defaultInferContentType(
|
|
138
|
+
static defaultInferContentType(filePath, provided) {
|
|
111
139
|
return succeed(undefined);
|
|
112
140
|
}
|
|
113
141
|
/**
|
|
@@ -119,7 +147,7 @@ export class FileItem {
|
|
|
119
147
|
* @remarks This default implementation always returns `Success` with `undefined`.
|
|
120
148
|
* @public
|
|
121
149
|
*/
|
|
122
|
-
static defaultAcceptContentType(
|
|
150
|
+
static defaultAcceptContentType(filePath, provided) {
|
|
123
151
|
return succeed(provided);
|
|
124
152
|
}
|
|
125
153
|
}
|
|
@@ -19,5 +19,28 @@
|
|
|
19
19
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if accessors support mutation.
|
|
24
|
+
* @param accessors - The accessors to check.
|
|
25
|
+
* @returns `true` if the accessors implement {@link FileTree.IMutableFileTreeAccessors}.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export function isMutableAccessors(accessors) {
|
|
29
|
+
const mutable = accessors;
|
|
30
|
+
return typeof mutable.fileIsMutable === 'function' && typeof mutable.saveFileContents === 'function';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Type guard to check if accessors support persistence.
|
|
34
|
+
* @param accessors - The accessors to check.
|
|
35
|
+
* @returns `true` if the accessors implement {@link FileTree.IPersistentFileTreeAccessors}.
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export function isPersistentAccessors(accessors) {
|
|
39
|
+
const persistent = accessors;
|
|
40
|
+
/* c8 ignore next 6 - no current accessor implements IPersistentFileTreeAccessors */
|
|
41
|
+
return (isMutableAccessors(accessors) &&
|
|
42
|
+
typeof persistent.syncToDisk === 'function' &&
|
|
43
|
+
typeof persistent.isDirty === 'function' &&
|
|
44
|
+
typeof persistent.getDirtyPaths === 'function');
|
|
45
|
+
}
|
|
23
46
|
//# sourceMappingURL=fileTreeAccessors.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a path matches a single pattern (string or RegExp).
|
|
24
|
+
* @param path - The path to check.
|
|
25
|
+
* @param pattern - The pattern to match against.
|
|
26
|
+
* @returns `true` if the path matches the pattern.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
function matchesPattern(path, pattern) {
|
|
30
|
+
if (typeof pattern === 'string') {
|
|
31
|
+
return path === pattern || path.startsWith(pattern + '/') || path.includes(pattern);
|
|
32
|
+
}
|
|
33
|
+
return pattern.test(path);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a path matches any pattern in an array.
|
|
37
|
+
* @param path - The path to check.
|
|
38
|
+
* @param patterns - The patterns to match against.
|
|
39
|
+
* @returns `true` if the path matches any pattern.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
function matchesAny(path, patterns) {
|
|
43
|
+
if (!patterns || patterns.length === 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return patterns.some((pattern) => matchesPattern(path, pattern));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a path is allowed by a mutability configuration.
|
|
50
|
+
* @param path - The path to check.
|
|
51
|
+
* @param mutable - The mutability configuration.
|
|
52
|
+
* @returns `true` if the path is mutable according to the configuration.
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export function isPathMutable(path, mutable) {
|
|
56
|
+
if (mutable === undefined || mutable === false) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (mutable === true) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const { include, exclude } = mutable;
|
|
63
|
+
// If exclude patterns are specified and path matches, it's not mutable
|
|
64
|
+
if (matchesAny(path, exclude)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// If include patterns are specified, path must match at least one
|
|
68
|
+
if (include && include.length > 0) {
|
|
69
|
+
return matchesAny(path, include);
|
|
70
|
+
}
|
|
71
|
+
// No include patterns means all paths (not excluded) are mutable
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=filterSpec.js.map
|
|
@@ -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,63 @@ 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
|
+
}
|
|
152
|
+
catch (_a) {
|
|
153
|
+
return failWithDetail(`${absolutePath}: permission denied`, 'permission-denied');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
|
|
158
|
+
*/
|
|
159
|
+
saveFileContents(path, contents) {
|
|
160
|
+
return this.fileIsMutable(path).asResult.onSuccess(() => {
|
|
161
|
+
const absolutePath = this.resolveAbsolutePath(path);
|
|
162
|
+
return captureResult(() => {
|
|
163
|
+
fs.writeFileSync(absolutePath, contents, 'utf8');
|
|
164
|
+
return contents;
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
|
|
170
|
+
*/
|
|
171
|
+
createDirectory(dirPath) {
|
|
172
|
+
const absolutePath = this.resolveAbsolutePath(dirPath);
|
|
173
|
+
// Check if mutability is disabled
|
|
174
|
+
if (this._mutable === false) {
|
|
175
|
+
return fail(`${absolutePath}: mutability is disabled`);
|
|
176
|
+
}
|
|
177
|
+
return captureResult(() => {
|
|
178
|
+
fs.mkdirSync(absolutePath, { recursive: true });
|
|
179
|
+
return absolutePath;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
121
182
|
}
|
|
122
183
|
//# sourceMappingURL=fsTree.js.map
|