@openclaw/fs-safe 0.9.0 → 0.11.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/CHANGELOG.md +134 -0
- package/LICENSE +1 -0
- package/README.md +52 -4
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -5
- package/dist/archive-gzip-tail.d.ts +3 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +23 -3
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +16 -13
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -6
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +83 -74
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +23 -10
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +18 -14
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +13 -8
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +12 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +82 -45
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +22 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +69 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +10 -1
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +27 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +222 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +125 -0
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +9 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +135 -39
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +6 -4
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -13
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +4 -2
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +8 -4
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +413 -0
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +5 -8
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +19 -18
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +24 -25
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +56 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +30 -34
- package/dist/mutation-authority.d.ts +9 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +29 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +19 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +3 -1
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +5 -2
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +85 -53
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +7 -3
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +51 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +6 -5
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +53 -28
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +26 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +219 -0
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +6 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +16 -12
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +17 -51
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +358 -234
- package/dist/root-options.d.ts +77 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +59 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +13 -9
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +14 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +3 -5
- package/dist/root.d.ts +4 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -8
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +1 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +42 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +6 -4
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +17 -5
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +12 -6
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +9 -7
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -28
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +26 -0
- package/docs/advanced.md +22 -4
- package/docs/archive.md +44 -9
- package/docs/atomic.md +24 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +36 -1
- package/docs/copy.md +155 -0
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +53 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +4 -4
- package/docs/file-store.md +15 -0
- package/docs/guest.md +141 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install.md +31 -0
- package/docs/local-roots.md +8 -1
- package/docs/native-helper.md +10 -3
- package/docs/native.md +18 -1
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/permissions.md +29 -10
- package/docs/positional-read.md +63 -0
- package/docs/public-api.md +31 -2
- package/docs/root.md +196 -6
- package/docs/secure-file.md +2 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +12 -3
- package/docs/temp.md +35 -6
- package/docs/timing.md +2 -0
- package/docs/types.md +19 -12
- package/docs/walk.md +54 -5
- package/docs/writing.md +173 -5
- package/package.json +19 -8
package/dist/root-paths.js
CHANGED
|
@@ -5,6 +5,7 @@ import { formatErrorDetail } from "./error-detail.js";
|
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
|
|
7
7
|
import { root as openRoot } from "./root.js";
|
|
8
|
+
import { realpathSync } from "./realpath.js";
|
|
8
9
|
function invalidPath(scopeLabel) {
|
|
9
10
|
return {
|
|
10
11
|
ok: false,
|
|
@@ -12,12 +13,15 @@ function invalidPath(scopeLabel) {
|
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
function pathStaysWithinRoot(rootDir, candidatePath) {
|
|
16
|
+
if (process.platform !== "win32") {
|
|
17
|
+
return candidatePath !== rootDir && isPathInside(rootDir, candidatePath);
|
|
18
|
+
}
|
|
15
19
|
const relative = path.relative(rootDir, candidatePath);
|
|
16
20
|
return Boolean(relative) && !isPathRelativeEscape(relative);
|
|
17
21
|
}
|
|
18
22
|
async function resolveRealPathIfExists(targetPath) {
|
|
19
23
|
try {
|
|
20
|
-
return
|
|
24
|
+
return realpathSync.native(targetPath);
|
|
21
25
|
}
|
|
22
26
|
catch {
|
|
23
27
|
return undefined;
|
|
@@ -29,7 +33,7 @@ async function resolveTrustedRootRealPath(rootDir) {
|
|
|
29
33
|
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
|
|
30
34
|
return undefined;
|
|
31
35
|
}
|
|
32
|
-
return
|
|
36
|
+
return realpathSync.native(rootDir);
|
|
33
37
|
}
|
|
34
38
|
catch {
|
|
35
39
|
return undefined;
|
|
@@ -50,7 +54,7 @@ async function validateCanonicalPathWithinRoot(params) {
|
|
|
50
54
|
if (params.expect === "file" && candidateLstat.nlink > 1) {
|
|
51
55
|
return "invalid";
|
|
52
56
|
}
|
|
53
|
-
const candidateRealPath =
|
|
57
|
+
const candidateRealPath = realpathSync.native(params.candidatePath);
|
|
54
58
|
return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
|
|
55
59
|
}
|
|
56
60
|
catch (err) {
|
|
@@ -169,9 +173,9 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
169
173
|
return invalidPath(scopeLabel);
|
|
170
174
|
}
|
|
171
175
|
await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel });
|
|
172
|
-
const rootReal =
|
|
176
|
+
const rootReal = realpathSync.native(rootDir);
|
|
173
177
|
const nearestExistingPath = await resolveNearestExistingPath(targetPath);
|
|
174
|
-
const nearestExistingReal =
|
|
178
|
+
const nearestExistingReal = realpathSync.native(nearestExistingPath);
|
|
175
179
|
if (!isPathInside(rootReal, nearestExistingReal)) {
|
|
176
180
|
return invalidPath(scopeLabel);
|
|
177
181
|
}
|
|
@@ -202,12 +206,12 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
|
-
const currentReal =
|
|
209
|
+
const currentReal = realpathSync.native(current);
|
|
206
210
|
if (!isPathInside(rootReal, currentReal)) {
|
|
207
211
|
return invalidPath(scopeLabel);
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
|
-
const targetReal =
|
|
214
|
+
const targetReal = realpathSync.native(targetPath);
|
|
211
215
|
if (!isPathInside(rootReal, targetReal)) {
|
|
212
216
|
return invalidPath(scopeLabel);
|
|
213
217
|
}
|
|
@@ -309,7 +313,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
|
309
313
|
return lexicalPathResult;
|
|
310
314
|
}
|
|
311
315
|
try {
|
|
312
|
-
const resolvedExistingPath =
|
|
316
|
+
const resolvedExistingPath = realpathSync.native(raw);
|
|
313
317
|
const relativePath = path.relative(rootRealPath, resolvedExistingPath);
|
|
314
318
|
if (!isInRoot(relativePath)) {
|
|
315
319
|
return lexicalPathResult;
|
|
@@ -351,7 +355,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
|
351
355
|
scopeLabel: params.scopeLabel,
|
|
352
356
|
});
|
|
353
357
|
const existingPath = await resolveNearestExistingPath(pathResult.fallbackPath);
|
|
354
|
-
const existingRealPath =
|
|
358
|
+
const existingRealPath = realpathSync.native(existingPath);
|
|
355
359
|
if (!isPathInside(rootRealPath, existingRealPath)) {
|
|
356
360
|
return invalidPath(params.scopeLabel);
|
|
357
361
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type RootContext } from "./root-context.js";
|
|
2
|
+
import type { RootRemoveOptions } from "./root-options.js";
|
|
3
|
+
export declare function validateRemoveOptions(options: RootRemoveOptions): void;
|
|
4
|
+
export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=root-remove.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAS3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AA0CD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
5
|
+
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
8
|
+
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
9
|
+
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
10
|
+
import { assertRootIdentityCurrent } from "./root-context.js";
|
|
11
|
+
import { normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
|
|
12
|
+
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
13
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
14
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
16
|
+
const DEFAULT_MAX_ENTRIES = 100_000;
|
|
17
|
+
const DEFAULT_MAX_DEPTH = 64;
|
|
18
|
+
export function validateRemoveOptions(options) {
|
|
19
|
+
if (options.order !== undefined && options.order !== "filesystem" && options.order !== "sorted") {
|
|
20
|
+
throw new TypeError("remove order must be filesystem or sorted");
|
|
21
|
+
}
|
|
22
|
+
for (const key of ["maxEntries", "maxDepth"]) {
|
|
23
|
+
const value = options[key];
|
|
24
|
+
if (value !== undefined && value !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(value) || value < 0)) {
|
|
25
|
+
throw new RangeError(`${key} must be a non-negative safe integer`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!options.recursive && (options.maxEntries !== undefined || options.maxDepth !== undefined)) {
|
|
29
|
+
throw new TypeError("remove budgets require recursive: true");
|
|
30
|
+
}
|
|
31
|
+
if (!options.recursive && options.order !== undefined) {
|
|
32
|
+
throw new TypeError("remove order requires recursive: true");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function assertNotAborted(signal) {
|
|
36
|
+
try {
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// Preserve arbitrary abort reasons through Root's filesystem error mapping.
|
|
41
|
+
throw new MutationAuthorityError(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function removeOne(root, targetPath, options) {
|
|
45
|
+
let guard;
|
|
46
|
+
try {
|
|
47
|
+
guard = await createAsyncDirectoryGuard(path.dirname(targetPath));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (options.force && isNotFoundPathError(error)) {
|
|
51
|
+
await assertRootIdentityCurrent(root);
|
|
52
|
+
assertNotAborted(options.signal);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
throw normalizeRemoveGuardError(error);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", targetPath);
|
|
59
|
+
await assertAsyncDirectoryGuard(guard);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw normalizeRemoveGuardError(error);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const isDirectory = fsSync.lstatSync(targetPath).isDirectory();
|
|
66
|
+
assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
|
|
67
|
+
assertNotAborted(options.signal);
|
|
68
|
+
options.assertBeforeMutation?.();
|
|
69
|
+
await (isDirectory ? fs.rmdir(targetPath) : fs.unlink(targetPath));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!(options.force && isNotFoundPathError(error)))
|
|
73
|
+
throw normalizeRemovePathError(error);
|
|
74
|
+
}
|
|
75
|
+
await assertAsyncDirectoryGuard(guard).catch(error => { throw normalizeRemoveGuardError(error); });
|
|
76
|
+
assertNotAborted(options.signal);
|
|
77
|
+
}
|
|
78
|
+
export async function removePathInRootFallback(root, targetPath, options) {
|
|
79
|
+
assertNotAborted(options.signal);
|
|
80
|
+
if (!options.recursive) {
|
|
81
|
+
await removeOne(root, targetPath, options);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
85
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
86
|
+
const details = (target, phase) => ({
|
|
87
|
+
operation: "remove",
|
|
88
|
+
phase,
|
|
89
|
+
relativePath: path.relative(targetPath, target),
|
|
90
|
+
});
|
|
91
|
+
let examined = 0;
|
|
92
|
+
const rootGuard = await createAsyncDirectoryGuard(root.rootReal, { bigint: true });
|
|
93
|
+
if (!sameFileIdentityForCleanup(rootGuard.stat, root.rootIdentity))
|
|
94
|
+
throw rootPathChangedError();
|
|
95
|
+
const guards = [rootGuard];
|
|
96
|
+
let ancestor = root.rootReal;
|
|
97
|
+
const parentRelative = path.relative(root.rootReal, path.dirname(targetPath));
|
|
98
|
+
for (const segment of parentRelative.split(path.sep).filter(Boolean)) {
|
|
99
|
+
ancestor = path.join(ancestor, segment);
|
|
100
|
+
let guard;
|
|
101
|
+
try {
|
|
102
|
+
guard = await createAsyncDirectoryGuard(ancestor, { bigint: true });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (isNotFoundPathError(error)) {
|
|
106
|
+
assertCurrent();
|
|
107
|
+
assertNotAborted(options.signal);
|
|
108
|
+
if (options.force)
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
throw normalizeRemoveGuardError(error);
|
|
112
|
+
}
|
|
113
|
+
if (!isPathInside(guard.realPath, ancestor) || !isPathInside(ancestor, guard.realPath)) {
|
|
114
|
+
throw new FsSafeError("path-mismatch", "removal ancestor changed during operation");
|
|
115
|
+
}
|
|
116
|
+
guards.push(guard);
|
|
117
|
+
}
|
|
118
|
+
function assertCurrent() {
|
|
119
|
+
for (const guard of guards) {
|
|
120
|
+
try {
|
|
121
|
+
assertSyncDirectoryGuard(guard);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// force tolerates a vanished leaf, never a lost traversal boundary.
|
|
125
|
+
throw new FsSafeError("path-mismatch", "removal ancestor changed during operation", { cause: error });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const inspect = (target, expected) => {
|
|
130
|
+
assertCurrent();
|
|
131
|
+
try {
|
|
132
|
+
return inspectFileIdentitySync(() => fsSync.lstatSync(target, { bigint: true }), expected);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (options.force && isNotFoundPathError(error))
|
|
136
|
+
return;
|
|
137
|
+
throw normalizeRemovePathError(error, details(target, expected ? "remove" : "inspect"));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
async function visit(target, depth, entryCounted = false) {
|
|
141
|
+
assertNotAborted(options.signal);
|
|
142
|
+
if ((!entryCounted && examined >= maxEntries) || depth > maxDepth) {
|
|
143
|
+
throw new FsSafeError("too-large", "recursive removal budget exceeded", {
|
|
144
|
+
details: details(target, "inspect"),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (!entryCounted)
|
|
148
|
+
examined += 1;
|
|
149
|
+
await assertMutationNotDenied(target, options.denyMutations, { protectAncestors: true });
|
|
150
|
+
assertNotAborted(options.signal);
|
|
151
|
+
const initial = inspect(target);
|
|
152
|
+
if (!initial)
|
|
153
|
+
return;
|
|
154
|
+
assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "inspect"));
|
|
155
|
+
if (initial.isDirectory()) {
|
|
156
|
+
let directoryGuard;
|
|
157
|
+
try {
|
|
158
|
+
directoryGuard = await createAsyncDirectoryGuard(target, { bigint: true });
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
assertCurrent();
|
|
162
|
+
assertNotAborted(options.signal);
|
|
163
|
+
if (options.force && isNotFoundPathError(error))
|
|
164
|
+
return;
|
|
165
|
+
throw normalizeRemovePathError(error, details(target, "inspect"));
|
|
166
|
+
}
|
|
167
|
+
if (!isPathInside(directoryGuard.realPath, target) || !isPathInside(target, directoryGuard.realPath) ||
|
|
168
|
+
!sameFileIdentityForCleanup(directoryGuard.stat, initial)) {
|
|
169
|
+
throw new FsSafeError("path-mismatch", "removal directory changed during operation");
|
|
170
|
+
}
|
|
171
|
+
guards.push(directoryGuard);
|
|
172
|
+
try {
|
|
173
|
+
assertCurrent();
|
|
174
|
+
assertNotAborted(options.signal);
|
|
175
|
+
const handle = await fs.opendir(target, { bufferSize: 1 }).catch(error => {
|
|
176
|
+
if (options.force && isNotFoundPathError(error)) {
|
|
177
|
+
assertNotAborted(options.signal);
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
throw normalizeRemovePathError(error, details(target, "enumerate"));
|
|
181
|
+
});
|
|
182
|
+
if (handle) {
|
|
183
|
+
let failed = false;
|
|
184
|
+
let operationError;
|
|
185
|
+
try {
|
|
186
|
+
let names = options.order === "sorted" ? [] : undefined;
|
|
187
|
+
if (names && maxEntries === Infinity) {
|
|
188
|
+
assertCurrent();
|
|
189
|
+
assertNotAborted(options.signal);
|
|
190
|
+
const snapshot = await fs.readdir(target).catch(error => {
|
|
191
|
+
if (options.force && isNotFoundPathError(error))
|
|
192
|
+
return undefined;
|
|
193
|
+
throw normalizeRemovePathError(error, details(target, "enumerate"));
|
|
194
|
+
});
|
|
195
|
+
assertNotAborted(options.signal);
|
|
196
|
+
if (snapshot) {
|
|
197
|
+
assertCurrent();
|
|
198
|
+
names = snapshot;
|
|
199
|
+
examined += names.length;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
while (true) {
|
|
204
|
+
assertCurrent();
|
|
205
|
+
assertNotAborted(options.signal);
|
|
206
|
+
const entry = await handle.read().catch(error => {
|
|
207
|
+
if (options.force && isNotFoundPathError(error))
|
|
208
|
+
return undefined;
|
|
209
|
+
throw normalizeRemovePathError(error, details(target, "enumerate"));
|
|
210
|
+
});
|
|
211
|
+
if (entry === undefined) {
|
|
212
|
+
assertNotAborted(options.signal);
|
|
213
|
+
if (names)
|
|
214
|
+
names.length = 0;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
assertCurrent();
|
|
218
|
+
assertNotAborted(options.signal);
|
|
219
|
+
if (!entry)
|
|
220
|
+
break;
|
|
221
|
+
if (names) {
|
|
222
|
+
if (examined >= maxEntries) {
|
|
223
|
+
throw new FsSafeError("too-large", "recursive removal budget exceeded", {
|
|
224
|
+
details: details(path.join(target, entry.name), "enumerate"),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
examined += 1;
|
|
228
|
+
names.push(entry.name);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
await visit(path.join(target, entry.name), depth + 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (names) {
|
|
236
|
+
for (const name of names.sort()) {
|
|
237
|
+
await visit(path.join(target, name), depth + 1, true);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
failed = true;
|
|
243
|
+
operationError = error instanceof MutationAuthorityError ? error.rejection : error;
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
try {
|
|
248
|
+
// Close before rmdir, including on Windows and after revocation.
|
|
249
|
+
await handle.close();
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
const closeError = normalizeRemovePathError(error, details(target, "enumerate"));
|
|
253
|
+
if (failed)
|
|
254
|
+
throw createSuppressedError(closeError, operationError, "recursive removal and close both failed");
|
|
255
|
+
throw closeError;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
guards.pop();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// A tolerated missing-directory observation still rechecks its target and ancestors.
|
|
265
|
+
await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", target);
|
|
266
|
+
await assertMutationNotDenied(target, options.denyMutations, { protectAncestors: true });
|
|
267
|
+
assertNotAborted(options.signal);
|
|
268
|
+
if (!inspect(target, initial))
|
|
269
|
+
return;
|
|
270
|
+
assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "remove"));
|
|
271
|
+
assertNotAborted(options.signal);
|
|
272
|
+
options.assertBeforeMutation?.();
|
|
273
|
+
try {
|
|
274
|
+
await (initial.isDirectory() ? fs.rmdir(target) : fs.unlink(target));
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (!(options.force && isNotFoundPathError(error))) {
|
|
278
|
+
throw normalizeRemovePathError(error, details(target, "remove"));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
assertCurrent();
|
|
282
|
+
assertNotAborted(options.signal);
|
|
283
|
+
}
|
|
284
|
+
await assertRootIdentityCurrent(root);
|
|
285
|
+
await visit(targetPath, 0);
|
|
286
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FsSafeErrorDetails } from "./errors.js";
|
|
2
|
+
export type SymlinkPolicy = "reject" | "follow-within-root" | "follow-parents-within-root";
|
|
3
|
+
export type MutationSymlinkPolicy = "reject" | "follow-parents-within-root";
|
|
4
|
+
export declare function readSymlinkResolution(symlinks: SymlinkPolicy | undefined): {
|
|
5
|
+
rejectSymlinks: boolean;
|
|
6
|
+
rejectFinalSymlink: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function mutationSymlinkResolution(symlinks: MutationSymlinkPolicy | undefined): {
|
|
9
|
+
rejectSymlinks: boolean;
|
|
10
|
+
rejectFinalSymlink: boolean;
|
|
11
|
+
resolveCanonical: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function assertFinalSymlinkRejected(targetPath: string, reject: boolean | undefined, details?: FsSafeErrorDetails): void;
|
|
14
|
+
//# sourceMappingURL=root-symlink-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-symlink-policy.d.ts","sourceRoot":"","sources":["../src/root-symlink-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,GAAG,4BAA4B,CAAC;AAC3F,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,4BAA4B,CAAC;AAE5E,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS;IAErE,cAAc;IACd,kBAAkB;EAErB;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,SAAS;IAKjF,cAAc;IACd,kBAAkB;IAClB,gBAAgB;EAEnB;AAID,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,IAAI,CASN"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { isNotFoundPathError } from "./path.js";
|
|
4
|
+
export function readSymlinkResolution(symlinks) {
|
|
5
|
+
return {
|
|
6
|
+
rejectSymlinks: symlinks !== "follow-within-root" && symlinks !== "follow-parents-within-root",
|
|
7
|
+
rejectFinalSymlink: symlinks === "follow-parents-within-root",
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function mutationSymlinkResolution(symlinks) {
|
|
11
|
+
if (symlinks !== undefined && symlinks !== "reject" && symlinks !== "follow-parents-within-root") {
|
|
12
|
+
throw new RangeError("mutationSymlinks must be reject or follow-parents-within-root");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
rejectSymlinks: symlinks === "reject",
|
|
16
|
+
rejectFinalSymlink: symlinks !== undefined,
|
|
17
|
+
resolveCanonical: symlinks !== undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Call after parent fences and immediately before the mutation. This is a
|
|
21
|
+
// final-component check, not an atomic conditional rename against another process.
|
|
22
|
+
export function assertFinalSymlinkRejected(targetPath, reject, details) {
|
|
23
|
+
if (!reject)
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
if (fs.lstatSync(targetPath).isSymbolicLink()) {
|
|
27
|
+
throw new FsSafeError("symlink", "final symlink not allowed", { details });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!isNotFoundPathError(error))
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/root-walk.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RootDirectoryListing, RootDirectoryListingOptions } from "./root-directory-list.js";
|
|
2
|
+
import type { PathStat } from "./types.js";
|
|
2
3
|
export type RootWalkSymlinkPolicy = "skip" | "follow-within-root";
|
|
3
4
|
export type RootWalkLimitBehavior = "truncate" | "throw";
|
|
4
5
|
export type RootWalkDirectoryErrorBehavior = "throw" | "skip-and-report";
|
|
@@ -24,6 +25,7 @@ export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFil
|
|
|
24
25
|
export type RootWalkOptions = {
|
|
25
26
|
maxDepth?: number;
|
|
26
27
|
maxEntries?: number;
|
|
28
|
+
order?: "sorted" | "filesystem";
|
|
27
29
|
symlinkPolicy: RootWalkSymlinkPolicy;
|
|
28
30
|
signal?: AbortSignal;
|
|
29
31
|
limitBehavior?: RootWalkLimitBehavior;
|
|
@@ -32,9 +34,8 @@ export type RootWalkOptions = {
|
|
|
32
34
|
};
|
|
33
35
|
type RootWalkCapability = {
|
|
34
36
|
rootReal: string;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}): Promise<DirEntry[]>;
|
|
37
|
+
stat(relativePath: string): Promise<PathStat>;
|
|
38
|
+
list(relativePath: string, options: RootDirectoryListingOptions): Promise<RootDirectoryListing>;
|
|
38
39
|
};
|
|
39
40
|
export declare function walkRoot(root: RootWalkCapability, relativePath: string, options: RootWalkOptions): AsyncGenerator<RootWalkEntry>;
|
|
40
41
|
export {};
|
package/dist/root-walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAqK/B"}
|