@veryfront/ext-content-mdx 0.1.1186 → 0.1.1189
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/esm/_dnt.polyfills.d.ts +6 -6
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/deno.d.ts +18 -0
- package/esm/deno.js +30 -9
- package/esm/src/config/defaults.d.ts +31 -24
- package/esm/src/config/defaults.d.ts.map +1 -1
- package/esm/src/config/defaults.js +25 -18
- package/esm/src/errors/diagnostic-policy.d.ts +35 -0
- package/esm/src/errors/diagnostic-policy.d.ts.map +1 -0
- package/esm/src/errors/diagnostic-policy.js +100 -0
- package/esm/src/errors/error-registry/boundary.d.ts +2 -0
- package/esm/src/errors/error-registry/boundary.d.ts.map +1 -1
- package/esm/src/errors/error-registry/boundary.js +8 -0
- package/esm/src/errors/error-registry/config.d.ts.map +1 -1
- package/esm/src/errors/error-registry/config.js +3 -3
- package/esm/src/errors/error-registry/deploy.js +1 -1
- package/esm/src/errors/error-registry/server.d.ts +2 -0
- package/esm/src/errors/error-registry/server.d.ts.map +1 -1
- package/esm/src/errors/error-registry/server.js +8 -0
- package/esm/src/errors/error-registry-helpers.d.ts +11 -2
- package/esm/src/errors/error-registry-helpers.d.ts.map +1 -1
- package/esm/src/errors/error-registry-helpers.js +70 -1
- package/esm/src/errors/error-registry.d.ts +85 -73
- package/esm/src/errors/error-registry.d.ts.map +1 -1
- package/esm/src/errors/error-registry.js +2 -14
- package/esm/src/errors/types.d.ts +44 -5
- package/esm/src/errors/types.d.ts.map +1 -1
- package/esm/src/errors/types.js +148 -24
- package/esm/src/errors/veryfront-error.d.ts +25 -2
- package/esm/src/errors/veryfront-error.d.ts.map +1 -1
- package/esm/src/errors/veryfront-error.js +638 -28
- package/esm/src/observability/limits.d.ts +20 -0
- package/esm/src/observability/limits.d.ts.map +1 -0
- package/esm/src/observability/limits.js +19 -0
- package/esm/src/observability/telemetry-error.d.ts +22 -0
- package/esm/src/observability/telemetry-error.d.ts.map +1 -0
- package/esm/src/observability/telemetry-error.js +377 -0
- package/esm/src/observability/tracing/api-shim.d.ts +39 -0
- package/esm/src/observability/tracing/api-shim.d.ts.map +1 -1
- package/esm/src/observability/tracing/api-shim.js +196 -69
- package/esm/src/observability/tracing/context-callback.d.ts +14 -0
- package/esm/src/observability/tracing/context-callback.d.ts.map +1 -0
- package/esm/src/observability/tracing/context-callback.js +111 -0
- package/esm/src/observability/tracing/otlp-setup.d.ts.map +1 -1
- package/esm/src/observability/tracing/otlp-setup.js +190 -46
- package/esm/src/platform/adapters/base.d.ts +51 -7
- package/esm/src/platform/adapters/base.d.ts.map +1 -1
- package/esm/src/platform/adapters/base.js +4 -2
- package/esm/src/platform/adapters/bounded-file-read.d.ts +9 -0
- package/esm/src/platform/adapters/bounded-file-read.d.ts.map +1 -1
- package/esm/src/platform/adapters/bounded-file-read.js +11 -5
- package/esm/src/platform/adapters/file-snapshot-error.d.ts +2 -0
- package/esm/src/platform/adapters/file-snapshot-error.d.ts.map +1 -1
- package/esm/src/platform/adapters/file-snapshot-error.js +2 -0
- package/esm/src/platform/adapters/file-system-capabilities.d.ts +74 -0
- package/esm/src/platform/adapters/file-system-capabilities.d.ts.map +1 -0
- package/esm/src/platform/adapters/file-system-capabilities.js +508 -0
- package/esm/src/platform/adapters/native-file-system-provenance.d.ts +6 -0
- package/esm/src/platform/adapters/native-file-system-provenance.d.ts.map +1 -0
- package/esm/src/platform/adapters/native-file-system-provenance.js +15 -0
- package/esm/src/platform/adapters/runtime/deno/filesystem-adapter.d.ts +45 -0
- package/esm/src/platform/adapters/runtime/deno/filesystem-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/deno/filesystem-adapter.js +291 -0
- package/esm/src/platform/adapters/runtime/shared/node-filesystem-adapter.d.ts +71 -0
- package/esm/src/platform/adapters/runtime/shared/node-filesystem-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/node-filesystem-adapter.js +471 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.d.ts +16 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.js +140 -0
- package/esm/src/platform/adapters/runtime/shared/temp-dir.d.ts +2 -0
- package/esm/src/platform/adapters/runtime/shared/temp-dir.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/temp-dir.js +10 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.d.ts +5 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.js +75 -0
- package/esm/src/platform/compat/abort-signal.d.ts +20 -0
- package/esm/src/platform/compat/abort-signal.d.ts.map +1 -0
- package/esm/src/platform/compat/abort-signal.js +64 -0
- package/esm/src/platform/compat/constants.d.ts +2 -2
- package/esm/src/platform/compat/constants.d.ts.map +1 -1
- package/esm/src/platform/compat/constants.js +2 -2
- package/esm/src/platform/compat/error-introspection.d.ts +37 -0
- package/esm/src/platform/compat/error-introspection.d.ts.map +1 -0
- package/esm/src/platform/compat/error-introspection.js +258 -0
- package/esm/src/platform/compat/fs.d.ts +29 -8
- package/esm/src/platform/compat/fs.d.ts.map +1 -1
- package/esm/src/platform/compat/fs.js +123 -57
- package/esm/src/platform/compat/native-brand-checks.d.ts +14 -0
- package/esm/src/platform/compat/native-brand-checks.d.ts.map +1 -0
- package/esm/src/platform/compat/native-brand-checks.js +68 -0
- package/esm/src/platform/compat/not-found-error.d.ts +8 -3
- package/esm/src/platform/compat/not-found-error.d.ts.map +1 -1
- package/esm/src/platform/compat/not-found-error.js +164 -31
- package/esm/src/platform/compat/path/security.d.ts.map +1 -1
- package/esm/src/platform/compat/path/security.js +3 -5
- package/esm/src/platform/compat/process/command.d.ts +24 -1
- package/esm/src/platform/compat/process/command.d.ts.map +1 -1
- package/esm/src/platform/compat/process/command.js +393 -78
- package/esm/src/platform/compat/process/env.d.ts +11 -0
- package/esm/src/platform/compat/process/env.d.ts.map +1 -1
- package/esm/src/platform/compat/process/env.js +28 -38
- package/esm/src/platform/compat/process/global-error.d.ts +19 -0
- package/esm/src/platform/compat/process/global-error.d.ts.map +1 -0
- package/esm/src/platform/compat/process/global-error.js +57 -0
- package/esm/src/platform/compat/process/lifecycle.d.ts +10 -3
- package/esm/src/platform/compat/process/lifecycle.d.ts.map +1 -1
- package/esm/src/platform/compat/process/lifecycle.js +74 -23
- package/esm/src/platform/compat/process/project-env-contract.d.ts +19 -0
- package/esm/src/platform/compat/process/project-env-contract.d.ts.map +1 -0
- package/esm/src/platform/compat/process/project-env-contract.js +19 -0
- package/esm/src/platform/compat/runtime.d.ts +14 -5
- package/esm/src/platform/compat/runtime.d.ts.map +1 -1
- package/esm/src/platform/compat/runtime.js +34 -9
- package/esm/src/platform/compat/std/front-matter-yaml.d.ts +12 -0
- package/esm/src/platform/compat/std/front-matter-yaml.d.ts.map +1 -1
- package/esm/src/platform/compat/std/front-matter-yaml.js +35 -3
- package/esm/src/platform/compat/temp-directory-prefix.d.ts +8 -0
- package/esm/src/platform/compat/temp-directory-prefix.d.ts.map +1 -0
- package/esm/src/platform/compat/temp-directory-prefix.js +15 -0
- package/esm/src/utils/abort.d.ts +10 -0
- package/esm/src/utils/abort.d.ts.map +1 -0
- package/esm/src/utils/abort.js +35 -0
- package/esm/src/utils/constants/build.d.ts +3 -0
- package/esm/src/utils/constants/build.d.ts.map +1 -1
- package/esm/src/utils/constants/build.js +5 -1
- package/esm/src/utils/constants/cache.d.ts +21 -0
- package/esm/src/utils/constants/cache.d.ts.map +1 -1
- package/esm/src/utils/constants/cache.js +101 -39
- package/esm/src/utils/constants/css.d.ts +20 -0
- package/esm/src/utils/constants/css.d.ts.map +1 -0
- package/esm/src/utils/constants/css.js +19 -0
- package/esm/src/utils/constants/limits.d.ts +4 -0
- package/esm/src/utils/constants/limits.d.ts.map +1 -1
- package/esm/src/utils/constants/limits.js +4 -0
- package/esm/src/utils/constants/project-identity.d.ts +5 -0
- package/esm/src/utils/constants/project-identity.d.ts.map +1 -0
- package/esm/src/utils/constants/project-identity.js +4 -0
- package/esm/src/utils/constants/retry.d.ts +18 -0
- package/esm/src/utils/constants/retry.d.ts.map +1 -1
- package/esm/src/utils/constants/retry.js +18 -0
- package/esm/src/utils/constants/security.d.ts +4 -0
- package/esm/src/utils/constants/security.d.ts.map +1 -1
- package/esm/src/utils/constants/security.js +4 -0
- package/esm/src/utils/index.d.ts +3 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +3 -1
- package/esm/src/utils/logger/core.d.ts +5 -0
- package/esm/src/utils/logger/core.d.ts.map +1 -1
- package/esm/src/utils/logger/core.js +67 -2
- package/esm/src/utils/logger/redact.d.ts +36 -13
- package/esm/src/utils/logger/redact.d.ts.map +1 -1
- package/esm/src/utils/logger/redact.js +573 -83
- package/esm/src/utils/parallel.d.ts.map +1 -1
- package/esm/src/utils/parallel.js +11 -1
- package/esm/src/utils/project-identity.d.ts +11 -0
- package/esm/src/utils/project-identity.d.ts.map +1 -0
- package/esm/src/utils/project-identity.js +69 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +3 -3
- package/esm/src/platform/adapters/runtime/shared/native-file-capabilities.d.ts +0 -33
- package/esm/src/platform/adapters/runtime/shared/native-file-capabilities.d.ts.map +0 -1
- package/esm/src/platform/adapters/runtime/shared/native-file-capabilities.js +0 -205
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
import * as dntShim from "../../../_dnt.shims.js";
|
|
2
2
|
import { createError, toError } from "../../errors/veryfront-error.js";
|
|
3
3
|
import { isBun, isDeno, isNode } from "./runtime.js";
|
|
4
|
+
import { validateTempDirectoryPrefix } from "./temp-directory-prefix.js";
|
|
4
5
|
import { readFileWithinLimit } from "../adapters/bounded-file-read.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
6
|
+
import { captureExclusiveCreateCapability, captureSnapshotReadCapability, } from "../adapters/file-system-capabilities.js";
|
|
7
|
+
import { isProxyWithoutHooks } from "./error-introspection.js";
|
|
8
|
+
import { isNotFoundError } from "./not-found-error.js";
|
|
9
|
+
export { isNotFoundError };
|
|
10
|
+
const DEFAULT_TEMP_DIRECTORY_PREFIX = "tmp-";
|
|
11
|
+
const UNSUPPORTED_CHMOD_ERROR_CODES = new Set([
|
|
12
|
+
"ENOSYS",
|
|
13
|
+
"ENOTSUP",
|
|
14
|
+
"EOPNOTSUPP",
|
|
15
|
+
]);
|
|
16
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
17
|
+
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
18
|
+
const reflectApply = Reflect.apply;
|
|
19
|
+
function hasOwnDataValue(value, key, expected) {
|
|
20
|
+
const descriptor = getOwnPropertyDescriptor(value, key);
|
|
21
|
+
return descriptor !== undefined &&
|
|
22
|
+
reflectApply(hasOwnProperty, descriptor, ["value"]) === true &&
|
|
23
|
+
descriptor.value === expected;
|
|
24
|
+
}
|
|
25
|
+
function isUnsupportedChmodError(error) {
|
|
26
|
+
if (!(error instanceof Error))
|
|
27
|
+
return false;
|
|
28
|
+
if (error.name === "NotSupported")
|
|
29
|
+
return true;
|
|
30
|
+
const code = error.code;
|
|
31
|
+
return typeof code === "string" && UNSUPPORTED_CHMOD_ERROR_CODES.has(code);
|
|
32
|
+
}
|
|
33
|
+
function normalizePathIdentity(device, inode) {
|
|
34
|
+
const isValidPart = (value) => (typeof value === "bigint" && value >= 0n) ||
|
|
35
|
+
(typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
|
|
36
|
+
if (!isValidPart(device) || !isValidPart(inode))
|
|
37
|
+
return undefined;
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
device: String(device),
|
|
40
|
+
inode: String(inode),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
7
43
|
/**
|
|
8
44
|
* Typed accessor for the Deno global.
|
|
9
45
|
*
|
|
@@ -16,9 +52,6 @@ function denoGlobal() {
|
|
|
16
52
|
return dntShim.dntGlobalThis.Deno;
|
|
17
53
|
}
|
|
18
54
|
class NodeFileSystem {
|
|
19
|
-
readFileSnapshotWithinLimit = supportsNativeFileSnapshots()
|
|
20
|
-
? (path, containmentRoot, byteLimit) => readNodeFileSnapshotWithinLimit(path, containmentRoot, byteLimit)
|
|
21
|
-
: undefined;
|
|
22
55
|
fs;
|
|
23
56
|
os;
|
|
24
57
|
path;
|
|
@@ -72,10 +105,7 @@ class NodeFileSystem {
|
|
|
72
105
|
const handle = await this.getFs().open(path, "r");
|
|
73
106
|
return {
|
|
74
107
|
close: () => handle.close(),
|
|
75
|
-
read: async (buffer) =>
|
|
76
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, null);
|
|
77
|
-
return bytesRead === 0 ? null : bytesRead;
|
|
78
|
-
},
|
|
108
|
+
read: async (buffer) => (await handle.read(buffer)).bytesRead,
|
|
79
109
|
};
|
|
80
110
|
}, byteLimit);
|
|
81
111
|
}
|
|
@@ -87,10 +117,6 @@ class NodeFileSystem {
|
|
|
87
117
|
await this.ensureInitialized();
|
|
88
118
|
await this.getFs().writeFile(path, data);
|
|
89
119
|
}
|
|
90
|
-
async createFileBytesExclusive(path, data) {
|
|
91
|
-
await this.ensureInitialized();
|
|
92
|
-
await this.getFs().writeFile(path, data, { flag: "wx" });
|
|
93
|
-
}
|
|
94
120
|
async rename(from, to) {
|
|
95
121
|
await this.ensureInitialized();
|
|
96
122
|
await this.getFs().rename(from, to);
|
|
@@ -102,7 +128,7 @@ class NodeFileSystem {
|
|
|
102
128
|
return true;
|
|
103
129
|
}
|
|
104
130
|
catch (error) {
|
|
105
|
-
if (error
|
|
131
|
+
if (isNotFoundError(error))
|
|
106
132
|
return false;
|
|
107
133
|
throw error;
|
|
108
134
|
}
|
|
@@ -148,30 +174,30 @@ class NodeFileSystem {
|
|
|
148
174
|
async remove(path, options) {
|
|
149
175
|
await this.ensureInitialized();
|
|
150
176
|
const recursive = options?.recursive ?? false;
|
|
151
|
-
await this.getFs().rm(path, { recursive, force:
|
|
177
|
+
await this.getFs().rm(path, { recursive, force: false });
|
|
152
178
|
}
|
|
153
179
|
async makeTempDir(options) {
|
|
154
180
|
await this.ensureInitialized();
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
181
|
+
const prefix = validateTempDirectoryPrefix(options?.prefix ?? DEFAULT_TEMP_DIRECTORY_PREFIX);
|
|
182
|
+
const tempRoot = this.getOs().tmpdir();
|
|
183
|
+
const separator = this.getPath().sep;
|
|
184
|
+
const tempRootPrefix = tempRoot.endsWith(separator) ? tempRoot : `${tempRoot}${separator}`;
|
|
185
|
+
return await this.getFs().mkdtemp(`${tempRootPrefix}${prefix}`);
|
|
158
186
|
}
|
|
159
187
|
async chmod(path, mode) {
|
|
160
188
|
await this.ensureInitialized();
|
|
161
189
|
try {
|
|
162
190
|
await this.getFs().chmod(path, mode);
|
|
163
191
|
}
|
|
164
|
-
catch {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (this.getOs().platform() === "win32" && isUnsupportedChmodError(error)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
199
|
}
|
|
171
200
|
class DenoFileSystem {
|
|
172
|
-
readFileSnapshotWithinLimit = supportsNativeFileSnapshots()
|
|
173
|
-
? (path, containmentRoot, byteLimit) => readNodeFileSnapshotWithinLimit(path, containmentRoot, byteLimit)
|
|
174
|
-
: undefined;
|
|
175
201
|
readTextFile(path) {
|
|
176
202
|
return denoGlobal().readTextFile(path);
|
|
177
203
|
}
|
|
@@ -181,7 +207,10 @@ class DenoFileSystem {
|
|
|
181
207
|
async readFileBytesWithinLimit(path, byteLimit) {
|
|
182
208
|
return await readFileWithinLimit(async () => {
|
|
183
209
|
const file = await denoGlobal().open(path, { read: true });
|
|
184
|
-
return {
|
|
210
|
+
return {
|
|
211
|
+
close: () => file.close(),
|
|
212
|
+
read: (buffer) => file.read(buffer),
|
|
213
|
+
};
|
|
185
214
|
}, byteLimit);
|
|
186
215
|
}
|
|
187
216
|
async writeTextFile(path, data) {
|
|
@@ -190,22 +219,6 @@ class DenoFileSystem {
|
|
|
190
219
|
async writeFile(path, data) {
|
|
191
220
|
await denoGlobal().writeFile(path, data);
|
|
192
221
|
}
|
|
193
|
-
async createFileBytesExclusive(path, data) {
|
|
194
|
-
const file = await denoGlobal().open(path, { write: true, createNew: true });
|
|
195
|
-
let offset = 0;
|
|
196
|
-
try {
|
|
197
|
-
while (offset < data.byteLength) {
|
|
198
|
-
const written = await file.write(data.subarray(offset));
|
|
199
|
-
if (!Number.isSafeInteger(written) || written <= 0 || written > data.byteLength - offset) {
|
|
200
|
-
throw new Error("Deno exclusive create write made no forward progress");
|
|
201
|
-
}
|
|
202
|
-
offset += written;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
finally {
|
|
206
|
-
file.close();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
222
|
async rename(from, to) {
|
|
210
223
|
await denoGlobal().rename(from, to);
|
|
211
224
|
}
|
|
@@ -215,7 +228,7 @@ class DenoFileSystem {
|
|
|
215
228
|
return true;
|
|
216
229
|
}
|
|
217
230
|
catch (error) {
|
|
218
|
-
if (error
|
|
231
|
+
if (isNotFoundError(error))
|
|
219
232
|
return false;
|
|
220
233
|
throw error;
|
|
221
234
|
}
|
|
@@ -256,21 +269,52 @@ class DenoFileSystem {
|
|
|
256
269
|
async remove(path, options) {
|
|
257
270
|
await denoGlobal().remove(path, { recursive: options?.recursive ?? false });
|
|
258
271
|
}
|
|
259
|
-
makeTempDir(options) {
|
|
260
|
-
|
|
272
|
+
async makeTempDir(options) {
|
|
273
|
+
const prefix = validateTempDirectoryPrefix(options?.prefix ?? DEFAULT_TEMP_DIRECTORY_PREFIX);
|
|
274
|
+
return await denoGlobal().makeTempDir({ prefix });
|
|
261
275
|
}
|
|
262
276
|
async chmod(path, mode) {
|
|
263
277
|
try {
|
|
264
278
|
await denoGlobal().chmod(path, mode);
|
|
265
279
|
}
|
|
266
|
-
catch (
|
|
267
|
-
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (denoGlobal().build.os === "windows" && isUnsupportedChmodError(error)) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
268
285
|
}
|
|
269
286
|
}
|
|
270
287
|
}
|
|
271
|
-
/** Create
|
|
288
|
+
/** Create the runtime-native filesystem implementation. */
|
|
272
289
|
export function createFileSystem() {
|
|
273
|
-
|
|
290
|
+
const fileSystem = isDeno ? new DenoFileSystem() : new NodeFileSystem();
|
|
291
|
+
let semanticAdapter;
|
|
292
|
+
const loadSemanticAdapter = () => semanticAdapter ??= isDeno
|
|
293
|
+
? import("../adapters/runtime/deno/filesystem-adapter.js")
|
|
294
|
+
.then(({ DenoFileSystemAdapter }) => new DenoFileSystemAdapter())
|
|
295
|
+
: import("../adapters/runtime/shared/node-filesystem-adapter.js")
|
|
296
|
+
.then(({ NodeCompatibleFileSystemAdapter }) => new NodeCompatibleFileSystemAdapter());
|
|
297
|
+
Object.defineProperty(fileSystem, "readFileSnapshotWithinLimit", {
|
|
298
|
+
value: async (path, containmentRoot, byteLimit) => {
|
|
299
|
+
const snapshotReader = captureSnapshotReadCapability(await loadSemanticAdapter(), "Native filesystem adapter");
|
|
300
|
+
if (snapshotReader === undefined) {
|
|
301
|
+
throw new Error("Native filesystem adapter does not support snapshot reads");
|
|
302
|
+
}
|
|
303
|
+
return await snapshotReader.read(path, containmentRoot, byteLimit);
|
|
304
|
+
},
|
|
305
|
+
enumerable: true,
|
|
306
|
+
});
|
|
307
|
+
Object.defineProperty(fileSystem, "createFileBytesExclusive", {
|
|
308
|
+
value: async (path, content) => {
|
|
309
|
+
const exclusiveCreator = captureExclusiveCreateCapability(await loadSemanticAdapter(), "Native filesystem adapter");
|
|
310
|
+
if (exclusiveCreator === undefined) {
|
|
311
|
+
throw new Error("Native filesystem adapter does not support exclusive creates");
|
|
312
|
+
}
|
|
313
|
+
await exclusiveCreator.create(path, content);
|
|
314
|
+
},
|
|
315
|
+
enumerable: true,
|
|
316
|
+
});
|
|
317
|
+
return fileSystem;
|
|
274
318
|
}
|
|
275
319
|
let _fs = null;
|
|
276
320
|
function getFs() {
|
|
@@ -293,7 +337,7 @@ export function writeTextFile(path, data) {
|
|
|
293
337
|
export function writeFile(path, data) {
|
|
294
338
|
return getFs().writeFile(path, data);
|
|
295
339
|
}
|
|
296
|
-
/**
|
|
340
|
+
/** Return false for a missing path and propagate every other filesystem error. */
|
|
297
341
|
export function exists(path) {
|
|
298
342
|
return getFs().exists(path);
|
|
299
343
|
}
|
|
@@ -323,11 +367,25 @@ export async function lstat(path) {
|
|
|
323
367
|
mtime: info.mtime,
|
|
324
368
|
};
|
|
325
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Read a path's native device/inode identity without following a terminal
|
|
372
|
+
* symbolic link. Returns undefined only when the runtime cannot expose a
|
|
373
|
+
* stable identity for the backing filesystem.
|
|
374
|
+
*/
|
|
375
|
+
export async function getPathIdentity(path) {
|
|
376
|
+
if (isDeno) {
|
|
377
|
+
const info = await denoGlobal().lstat(path);
|
|
378
|
+
return normalizePathIdentity(info.dev, info.ino);
|
|
379
|
+
}
|
|
380
|
+
const fs = await import("node:fs/promises");
|
|
381
|
+
const info = await fs.lstat(path, { bigint: true });
|
|
382
|
+
return normalizePathIdentity(info.dev, info.ino);
|
|
383
|
+
}
|
|
326
384
|
/** Create a directory. */
|
|
327
385
|
export function mkdir(path, options) {
|
|
328
386
|
return getFs().mkdir(path, options);
|
|
329
387
|
}
|
|
330
|
-
/** Remove a file or directory. */
|
|
388
|
+
/** Remove a file or directory, rejecting when the path does not exist. */
|
|
331
389
|
export function remove(path, options) {
|
|
332
390
|
return getFs().remove(path, options);
|
|
333
391
|
}
|
|
@@ -335,11 +393,11 @@ export function remove(path, options) {
|
|
|
335
393
|
export function readDir(path) {
|
|
336
394
|
return getFs().readDir(path);
|
|
337
395
|
}
|
|
338
|
-
/**
|
|
396
|
+
/** Atomically create a unique directory beneath the operating-system temp root. */
|
|
339
397
|
export function makeTempDir(options) {
|
|
340
398
|
return getFs().makeTempDir(options);
|
|
341
399
|
}
|
|
342
|
-
/** Change file permissions. */
|
|
400
|
+
/** Change file permissions, rejecting operational failures. */
|
|
343
401
|
export function chmod(path, mode) {
|
|
344
402
|
return getFs().chmod(path, mode);
|
|
345
403
|
}
|
|
@@ -365,8 +423,16 @@ export async function realPath(path) {
|
|
|
365
423
|
}
|
|
366
424
|
/** Error shape for is already exists. */
|
|
367
425
|
export function isAlreadyExistsError(error) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
426
|
+
if (typeof error !== "object" || error === null || isProxyWithoutHooks(error)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const AlreadyExists = isDeno ? denoGlobal().errors.AlreadyExists : undefined;
|
|
431
|
+
if (AlreadyExists && error instanceof AlreadyExists)
|
|
432
|
+
return true;
|
|
433
|
+
return hasOwnDataValue(error, "code", "EEXIST");
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
372
438
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface NativeBrandChecks {
|
|
2
|
+
isAsyncFunction(value: unknown): boolean;
|
|
3
|
+
isNativeError(value: unknown): boolean;
|
|
4
|
+
isPromise(value: unknown): boolean;
|
|
5
|
+
isProxy(value: unknown): boolean;
|
|
6
|
+
isUint8Array(value: unknown): boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Immutable host brand checks captured once at the runtime trust boundary.
|
|
10
|
+
* Browser and edge hosts without Node-compatible primitives remain linkable
|
|
11
|
+
* and use the conservative callers in error-introspection.ts.
|
|
12
|
+
*/
|
|
13
|
+
export declare const nativeBrandChecks: NativeBrandChecks | undefined;
|
|
14
|
+
//# sourceMappingURL=native-brand-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-brand-checks.d.ts","sourceRoot":"","sources":["../../../../src/src/platform/compat/native-brand-checks.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACzC,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACvC,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACnC,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACjC,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CACvC;AA+ED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,+BAE7B,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
2
|
+
import { isBun, isDeno, isNode } from "./runtime.js";
|
|
3
|
+
const apply = Reflect.apply;
|
|
4
|
+
const createObject = Object.create;
|
|
5
|
+
const defineProperty = Object.defineProperty;
|
|
6
|
+
const freeze = Object.freeze;
|
|
7
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
8
|
+
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
9
|
+
function hasOwn(object, key) {
|
|
10
|
+
return apply(objectHasOwnProperty, object, [key]);
|
|
11
|
+
}
|
|
12
|
+
function readOwnDataFunction(source, key) {
|
|
13
|
+
const descriptor = getOwnPropertyDescriptor(source, key);
|
|
14
|
+
if (!descriptor || !hasOwn(descriptor, "value"))
|
|
15
|
+
return undefined;
|
|
16
|
+
return typeof descriptor.value === "function" ? descriptor.value : undefined;
|
|
17
|
+
}
|
|
18
|
+
function snapshotNativeBrandChecks(value) {
|
|
19
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const snapshot = createObject(null);
|
|
23
|
+
for (const key of [
|
|
24
|
+
"isAsyncFunction",
|
|
25
|
+
"isNativeError",
|
|
26
|
+
"isPromise",
|
|
27
|
+
"isProxy",
|
|
28
|
+
"isUint8Array",
|
|
29
|
+
]) {
|
|
30
|
+
const check = readOwnDataFunction(value, key);
|
|
31
|
+
if (!check)
|
|
32
|
+
return undefined;
|
|
33
|
+
defineProperty(snapshot, key, {
|
|
34
|
+
configurable: false,
|
|
35
|
+
enumerable: true,
|
|
36
|
+
value: check,
|
|
37
|
+
writable: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return freeze(snapshot);
|
|
41
|
+
}
|
|
42
|
+
function loadNativeBrandCheckModule() {
|
|
43
|
+
if (!isBun && !isDeno && !isNode)
|
|
44
|
+
return undefined;
|
|
45
|
+
const hostProcess = dntShim.dntGlobalThis.process;
|
|
46
|
+
if (hostProcess) {
|
|
47
|
+
const getBuiltinModule = readOwnDataFunction(hostProcess, "getBuiltinModule");
|
|
48
|
+
if (getBuiltinModule) {
|
|
49
|
+
return apply(getBuiltinModule, hostProcess, ["node:util/types"]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Bun exposes synchronous require() in ESM. This retains compatibility with
|
|
53
|
+
// Bun releases that predate process.getBuiltinModule without adding a Node
|
|
54
|
+
// builtin to browser module graphs.
|
|
55
|
+
if (isBun && typeof require === "function") {
|
|
56
|
+
return apply(require, undefined, ["node:util/types"]);
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Immutable host brand checks captured once at the runtime trust boundary.
|
|
62
|
+
* Browser and edge hosts without Node-compatible primitives remain linkable
|
|
63
|
+
* and use the conservative callers in error-introspection.ts.
|
|
64
|
+
*/
|
|
65
|
+
export const nativeBrandChecks = snapshotNativeBrandChecks(loadNativeBrandCheckModule());
|
|
66
|
+
if ((isBun || isDeno || isNode) && !nativeBrandChecks) {
|
|
67
|
+
throw new Error("The current server runtime does not expose complete node:util/types brand checks");
|
|
68
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Strict absence classifier for fail-closed filesystem boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Every value in the inspected cause chain must carry the runtime's native
|
|
5
|
+
* Error brand. This keeps arbitrary error-shaped records such as
|
|
6
|
+
* `{ code: "ENOENT" }` from being converted into ordinary absence while still
|
|
7
|
+
* recognizing native Deno, Node, and Veryfront not-found errors without
|
|
8
|
+
* invoking project-owned hooks.
|
|
5
9
|
*/
|
|
10
|
+
export declare function isCanonicalNotFoundError(error: unknown): boolean;
|
|
6
11
|
export declare function isNotFoundError(error: unknown, seen?: Set<unknown>): boolean;
|
|
7
12
|
//# sourceMappingURL=not-found-error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"not-found-error.d.ts","sourceRoot":"","sources":["../../../../src/src/platform/compat/not-found-error.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"not-found-error.d.ts","sourceRoot":"","sources":["../../../../src/src/platform/compat/not-found-error.ts"],"names":[],"mappings":"AAqMA;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,GAAG,CAAC,OAAO,CAAa,GAAG,OAAO,CAEvF"}
|
|
@@ -1,41 +1,174 @@
|
|
|
1
1
|
import * as dntShim from "../../../_dnt.shims.js";
|
|
2
|
+
import { isNativeErrorWithoutHooks, isProxyWithoutHooks, readNativeErrorNameWithoutHooks, } from "./error-introspection.js";
|
|
2
3
|
import { isDeno } from "./runtime.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const MAX_CAUSE_DEPTH = 64;
|
|
5
|
+
const MAX_PROTOTYPE_DEPTH = 32;
|
|
6
|
+
const NO_DATA_VALUE = Symbol("no-data-value");
|
|
7
|
+
const NO_OWN_PROPERTY = Symbol("no-own-property");
|
|
8
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
9
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
10
|
+
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
11
|
+
const reflectApply = Reflect.apply;
|
|
12
|
+
const setAdd = Set.prototype.add;
|
|
13
|
+
const setHas = Set.prototype.has;
|
|
14
|
+
function hasOwn(value, key) {
|
|
15
|
+
return reflectApply(hasOwnProperty, value, [key]);
|
|
16
|
+
}
|
|
17
|
+
function readOwnDataValue(value, key) {
|
|
18
|
+
if (isProxyWithoutHooks(value))
|
|
19
|
+
return NO_DATA_VALUE;
|
|
12
20
|
try {
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
21
|
+
const descriptor = getOwnPropertyDescriptor(value, key);
|
|
22
|
+
if (!descriptor)
|
|
23
|
+
return NO_OWN_PROPERTY;
|
|
24
|
+
return hasOwn(descriptor, "value") ? descriptor.value : NO_DATA_VALUE;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return NO_DATA_VALUE;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function readDataValue(value, key) {
|
|
31
|
+
let owner = value;
|
|
32
|
+
for (let depth = 0; owner !== null && depth < MAX_PROTOTYPE_DEPTH; depth++) {
|
|
33
|
+
const result = readOwnDataValue(owner, key);
|
|
34
|
+
if (result === NO_DATA_VALUE)
|
|
35
|
+
return NO_DATA_VALUE;
|
|
36
|
+
if (result !== NO_OWN_PROPERTY)
|
|
37
|
+
return result;
|
|
38
|
+
if (isProxyWithoutHooks(owner))
|
|
39
|
+
return NO_DATA_VALUE;
|
|
40
|
+
try {
|
|
41
|
+
owner = getPrototypeOf(owner);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return NO_DATA_VALUE;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return NO_DATA_VALUE;
|
|
48
|
+
}
|
|
49
|
+
function readClassifiedDataValue(value, key, ownOnly) {
|
|
50
|
+
if (!ownOnly)
|
|
51
|
+
return readDataValue(value, key);
|
|
52
|
+
const result = readOwnDataValue(value, key);
|
|
53
|
+
return result === NO_OWN_PROPERTY ? NO_DATA_VALUE : result;
|
|
54
|
+
}
|
|
55
|
+
function captureDenoNotFoundPrototype() {
|
|
56
|
+
if (!isDeno)
|
|
57
|
+
return undefined;
|
|
58
|
+
const deno = readOwnDataValue(dntShim.dntGlobalThis, "Deno");
|
|
59
|
+
if (typeof deno !== "object" || deno === null)
|
|
60
|
+
return undefined;
|
|
61
|
+
const errors = readOwnDataValue(deno, "errors");
|
|
62
|
+
if (typeof errors !== "object" || errors === null)
|
|
63
|
+
return undefined;
|
|
64
|
+
const NotFound = readOwnDataValue(errors, "NotFound");
|
|
65
|
+
if (typeof NotFound !== "function" || isProxyWithoutHooks(NotFound))
|
|
66
|
+
return undefined;
|
|
67
|
+
const prototype = readOwnDataValue(NotFound, "prototype");
|
|
68
|
+
return typeof prototype === "object" && prototype !== null ? prototype : undefined;
|
|
69
|
+
}
|
|
70
|
+
const denoNotFoundPrototype = captureDenoNotFoundPrototype();
|
|
71
|
+
function inheritsFrom(value, expectedPrototype) {
|
|
72
|
+
let prototype = value;
|
|
73
|
+
for (let depth = 0; prototype !== null && depth < MAX_PROTOTYPE_DEPTH; depth++) {
|
|
74
|
+
if (isProxyWithoutHooks(prototype))
|
|
75
|
+
return false;
|
|
76
|
+
try {
|
|
77
|
+
prototype = getPrototypeOf(prototype);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (prototype === expectedPrototype)
|
|
15
83
|
return true;
|
|
16
|
-
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function normalizeSeenSet(seen) {
|
|
88
|
+
try {
|
|
89
|
+
reflectApply(setHas, seen, [seen]);
|
|
90
|
+
return seen;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return new Set();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function markSeen(seen, value) {
|
|
97
|
+
if (reflectApply(setHas, seen, [value]))
|
|
98
|
+
return false;
|
|
99
|
+
reflectApply(setAdd, seen, [value]);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
function isLegacyFileNotFoundError(error, name, ownDataOnly) {
|
|
103
|
+
if (name !== "VeryfrontError[file]")
|
|
104
|
+
return false;
|
|
105
|
+
const context = readClassifiedDataValue(error, "context", ownDataOnly);
|
|
106
|
+
if (typeof context !== "object" || context === null ||
|
|
107
|
+
isProxyWithoutHooks(context)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const type = readClassifiedDataValue(context, "type", ownDataOnly);
|
|
111
|
+
const message = readClassifiedDataValue(context, "message", ownDataOnly);
|
|
112
|
+
return type === "file" &&
|
|
113
|
+
typeof message === "string" &&
|
|
114
|
+
/^(?:File|Path) not found:/.test(message);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Return whether an error or its bounded cause chain represents a path that
|
|
118
|
+
* cannot be resolved. ENOTDIR is included because a missing candidate beneath
|
|
119
|
+
* a file is just as absent as an ENOENT candidate during filesystem lookup.
|
|
120
|
+
* Project-owned accessors and Proxy traps are never evaluated.
|
|
121
|
+
*/
|
|
122
|
+
function classifyNotFoundError(error, seen, requireNativeCauseChain) {
|
|
123
|
+
const visited = normalizeSeenSet(seen);
|
|
124
|
+
let current = error;
|
|
125
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
|
|
126
|
+
if (typeof current !== "object" || current === null ||
|
|
127
|
+
isProxyWithoutHooks(current) || !markSeen(visited, current)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (requireNativeCauseChain && !isNativeErrorWithoutHooks(current)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const code = readClassifiedDataValue(current, "code", requireNativeCauseChain);
|
|
17
134
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
18
135
|
return true;
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const legacyContext = error.context;
|
|
25
|
-
if (error.name === "VeryfrontError[file]" &&
|
|
26
|
-
typeof legacyContext === "object" &&
|
|
27
|
-
legacyContext !== null &&
|
|
28
|
-
legacyContext.type === "file" &&
|
|
29
|
-
typeof legacyContext.message === "string" &&
|
|
30
|
-
/^(?:File|Path) not found:/.test(legacyContext.message)) {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
if ("cause" in error)
|
|
34
|
-
return isNotFoundError(error.cause, seen);
|
|
136
|
+
if (!isNativeErrorWithoutHooks(current))
|
|
137
|
+
return false;
|
|
138
|
+
if (denoNotFoundPrototype && inheritsFrom(current, denoNotFoundPrototype)) {
|
|
139
|
+
return true;
|
|
35
140
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
141
|
+
const name = readNativeErrorNameWithoutHooks(current);
|
|
142
|
+
const structuralName = requireNativeCauseChain
|
|
143
|
+
? readClassifiedDataValue(current, "name", true)
|
|
144
|
+
: name;
|
|
145
|
+
if (structuralName === "VeryfrontError" &&
|
|
146
|
+
readClassifiedDataValue(current, "slug", requireNativeCauseChain) === "file-not-found") {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (typeof structuralName === "string" &&
|
|
150
|
+
isLegacyFileNotFoundError(current, structuralName, requireNativeCauseChain)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
const cause = readClassifiedDataValue(current, "cause", requireNativeCauseChain);
|
|
154
|
+
if (cause === NO_DATA_VALUE)
|
|
155
|
+
return false;
|
|
156
|
+
current = cause;
|
|
39
157
|
}
|
|
40
158
|
return false;
|
|
41
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Strict absence classifier for fail-closed filesystem boundaries.
|
|
162
|
+
*
|
|
163
|
+
* Every value in the inspected cause chain must carry the runtime's native
|
|
164
|
+
* Error brand. This keeps arbitrary error-shaped records such as
|
|
165
|
+
* `{ code: "ENOENT" }` from being converted into ordinary absence while still
|
|
166
|
+
* recognizing native Deno, Node, and Veryfront not-found errors without
|
|
167
|
+
* invoking project-owned hooks.
|
|
168
|
+
*/
|
|
169
|
+
export function isCanonicalNotFoundError(error) {
|
|
170
|
+
return classifyNotFoundError(error, new Set(), true);
|
|
171
|
+
}
|
|
172
|
+
export function isNotFoundError(error, seen = new Set()) {
|
|
173
|
+
return classifyNotFoundError(error, seen, false);
|
|
174
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../../../src/src/platform/compat/path/security.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../../../src/src/platform/compat/path/security.ts"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmB1D"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MAX_PATH_LENGTH, MAX_PATH_TRAVERSAL_DEPTH } from "../../../utils/constants/security.js";
|
|
2
2
|
export function validatePathSecurity(path) {
|
|
3
3
|
if (path == null)
|
|
4
4
|
return false;
|
|
5
5
|
if (path.length > MAX_PATH_LENGTH)
|
|
6
6
|
return false;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
7
|
+
if (path.includes("\0"))
|
|
8
|
+
return false;
|
|
11
9
|
const parts = path.split(/[\/\\]/);
|
|
12
10
|
let depth = 0;
|
|
13
11
|
for (const part of parts) {
|