@componentor/fs 3.0.40 → 3.0.42

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/index.js CHANGED
@@ -354,7 +354,10 @@ var OP = {
354
354
  FTRUNCATE: 27,
355
355
  FSYNC: 28,
356
356
  OPENDIR: 29,
357
- MKDTEMP: 30
357
+ MKDTEMP: 30,
358
+ FCHMOD: 31,
359
+ FCHOWN: 32,
360
+ FUTIMES: 33
358
361
  };
359
362
  var SAB_OFFSETS = {
360
363
  // Int32 - bytes in this chunk
@@ -1004,9 +1007,31 @@ function createFileHandle(fd, asyncRequest) {
1004
1007
  const { status } = await asyncRequest(OP.FWRITE, "", 0, null, void 0, { fd, data: encoded, position: st.size });
1005
1008
  if (status !== 0) throw statusToError(status, "write", String(fd));
1006
1009
  },
1007
- async chmod(_mode) {
1010
+ async chmod(mode) {
1011
+ const payload = new Uint8Array(8);
1012
+ const dv = new DataView(payload.buffer);
1013
+ dv.setUint32(0, fd, true);
1014
+ dv.setUint32(4, mode, true);
1015
+ const { status } = await asyncRequest(OP.FCHMOD, "", 0, payload);
1016
+ if (status !== 0) throw statusToError(status, "fchmod", String(fd));
1008
1017
  },
1009
- async chown(_uid, _gid) {
1018
+ async chown(uid, gid) {
1019
+ const payload = new Uint8Array(12);
1020
+ const dv = new DataView(payload.buffer);
1021
+ dv.setUint32(0, fd, true);
1022
+ dv.setUint32(4, uid, true);
1023
+ dv.setUint32(8, gid, true);
1024
+ const { status } = await asyncRequest(OP.FCHOWN, "", 0, payload);
1025
+ if (status !== 0) throw statusToError(status, "fchown", String(fd));
1026
+ },
1027
+ async utimes(atime, mtime) {
1028
+ const payload = new Uint8Array(24);
1029
+ const dv = new DataView(payload.buffer);
1030
+ dv.setUint32(0, fd, true);
1031
+ dv.setFloat64(8, typeof atime === "number" ? atime : atime.getTime(), true);
1032
+ dv.setFloat64(16, typeof mtime === "number" ? mtime : mtime.getTime(), true);
1033
+ const { status } = await asyncRequest(OP.FUTIMES, "", 0, payload);
1034
+ if (status !== 0) throw statusToError(status, "futimes", String(fd));
1010
1035
  },
1011
1036
  async sync() {
1012
1037
  await asyncRequest(OP.FSYNC, "");
@@ -1209,6 +1234,23 @@ async function chmod(asyncRequest, filePath, mode) {
1209
1234
  const { status } = await asyncRequest(OP.CHMOD, filePath, 0, modeBuf);
1210
1235
  if (status !== 0) throw statusToError(status, "chmod", filePath);
1211
1236
  }
1237
+ function fchmodSync(syncRequest, fd, mode) {
1238
+ const payload = new Uint8Array(8);
1239
+ const dv = new DataView(payload.buffer);
1240
+ dv.setUint32(0, fd, true);
1241
+ dv.setUint32(4, mode, true);
1242
+ const buf = encodeRequest(OP.FCHMOD, "", 0, payload);
1243
+ const { status } = syncRequest(buf);
1244
+ if (status !== 0) throw statusToError(status, "fchmod", String(fd));
1245
+ }
1246
+ async function fchmod(asyncRequest, fd, mode) {
1247
+ const payload = new Uint8Array(8);
1248
+ const dv = new DataView(payload.buffer);
1249
+ dv.setUint32(0, fd, true);
1250
+ dv.setUint32(4, mode, true);
1251
+ const { status } = await asyncRequest(OP.FCHMOD, "", 0, payload);
1252
+ if (status !== 0) throw statusToError(status, "fchmod", String(fd));
1253
+ }
1212
1254
 
1213
1255
  // src/methods/writeFile.ts
1214
1256
  var encoder3 = new TextEncoder();
@@ -1656,6 +1698,25 @@ async function chown(asyncRequest, filePath, uid, gid) {
1656
1698
  const { status } = await asyncRequest(OP.CHOWN, filePath, 0, buf);
1657
1699
  if (status !== 0) throw statusToError(status, "chown", filePath);
1658
1700
  }
1701
+ function fchownSync(syncRequest, fd, uid, gid) {
1702
+ const payload = new Uint8Array(12);
1703
+ const dv = new DataView(payload.buffer);
1704
+ dv.setUint32(0, fd, true);
1705
+ dv.setUint32(4, uid, true);
1706
+ dv.setUint32(8, gid, true);
1707
+ const buf = encodeRequest(OP.FCHOWN, "", 0, payload);
1708
+ const { status } = syncRequest(buf);
1709
+ if (status !== 0) throw statusToError(status, "fchown", String(fd));
1710
+ }
1711
+ async function fchown(asyncRequest, fd, uid, gid) {
1712
+ const payload = new Uint8Array(12);
1713
+ const dv = new DataView(payload.buffer);
1714
+ dv.setUint32(0, fd, true);
1715
+ dv.setUint32(4, uid, true);
1716
+ dv.setUint32(8, gid, true);
1717
+ const { status } = await asyncRequest(OP.FCHOWN, "", 0, payload);
1718
+ if (status !== 0) throw statusToError(status, "fchown", String(fd));
1719
+ }
1659
1720
 
1660
1721
  // src/methods/utimes.ts
1661
1722
  function utimesSync(syncRequest, filePath, atime, mtime) {
@@ -1675,6 +1736,25 @@ async function utimes(asyncRequest, filePath, atime, mtime) {
1675
1736
  const { status } = await asyncRequest(OP.UTIMES, filePath, 0, buf);
1676
1737
  if (status !== 0) throw statusToError(status, "utimes", filePath);
1677
1738
  }
1739
+ function futimesSync(syncRequest, fd, atime, mtime) {
1740
+ const payload = new Uint8Array(24);
1741
+ const dv = new DataView(payload.buffer);
1742
+ dv.setUint32(0, fd, true);
1743
+ dv.setFloat64(8, typeof atime === "number" ? atime : atime.getTime(), true);
1744
+ dv.setFloat64(16, typeof mtime === "number" ? mtime : mtime.getTime(), true);
1745
+ const buf = encodeRequest(OP.FUTIMES, "", 0, payload);
1746
+ const { status } = syncRequest(buf);
1747
+ if (status !== 0) throw statusToError(status, "futimes", String(fd));
1748
+ }
1749
+ async function futimes(asyncRequest, fd, atime, mtime) {
1750
+ const payload = new Uint8Array(24);
1751
+ const dv = new DataView(payload.buffer);
1752
+ dv.setUint32(0, fd, true);
1753
+ dv.setFloat64(8, typeof atime === "number" ? atime : atime.getTime(), true);
1754
+ dv.setFloat64(16, typeof mtime === "number" ? mtime : mtime.getTime(), true);
1755
+ const { status } = await asyncRequest(OP.FUTIMES, "", 0, payload);
1756
+ if (status !== 0) throw statusToError(status, "futimes", String(fd));
1757
+ }
1678
1758
 
1679
1759
  // src/methods/symlink.ts
1680
1760
  var encoder7 = new TextEncoder();
@@ -1908,12 +1988,8 @@ function onBroadcast(event) {
1908
1988
  const { eventType, path: mutatedPath } = event.data;
1909
1989
  for (const entry of watchers) {
1910
1990
  const filename = matchWatcher(entry, mutatedPath);
1911
- if (filename !== null) {
1912
- try {
1913
- entry.listener(eventType, filename);
1914
- } catch {
1915
- }
1916
- }
1991
+ if (filename === null) continue;
1992
+ queueWatchEvent(entry, eventType, filename);
1917
1993
  }
1918
1994
  const fileSet = fileWatchers.get(mutatedPath);
1919
1995
  if (fileSet) {
@@ -1922,6 +1998,29 @@ function onBroadcast(event) {
1922
1998
  }
1923
1999
  }
1924
2000
  }
2001
+ function queueWatchEvent(entry, eventType, filename) {
2002
+ const key = eventType + ":" + filename;
2003
+ if (!entry.pendingEvents) {
2004
+ entry.pendingEvents = /* @__PURE__ */ new Set();
2005
+ queueMicrotask(() => {
2006
+ const pending = entry.pendingEvents;
2007
+ entry.pendingEvents = null;
2008
+ for (const k of pending) {
2009
+ const colon = k.indexOf(":");
2010
+ const et = k.slice(0, colon);
2011
+ const name = k.slice(colon + 1);
2012
+ try {
2013
+ entry.listener(et, entry.asBuffer ? encodeFilename(name) : name);
2014
+ } catch {
2015
+ }
2016
+ }
2017
+ });
2018
+ }
2019
+ entry.pendingEvents.add(key);
2020
+ }
2021
+ function encodeFilename(name) {
2022
+ return new TextEncoder().encode(name);
2023
+ }
1925
2024
  function matchWatcher(entry, mutatedPath) {
1926
2025
  const { absPath, recursive } = entry;
1927
2026
  if (mutatedPath === absPath) {
@@ -1936,17 +2035,20 @@ function matchWatcher(entry, mutatedPath) {
1936
2035
  return relativePath.indexOf("/") === -1 ? relativePath : null;
1937
2036
  }
1938
2037
  function watch(ns, filePath, options, listener) {
1939
- const opts = typeof options === "string" ? { } : options ?? {};
2038
+ const opts = typeof options === "string" ? { encoding: options } : options ?? {};
1940
2039
  const cb = listener ?? (() => {
1941
2040
  });
1942
2041
  const absPath = resolve(filePath);
1943
2042
  const signal = opts.signal;
2043
+ const asBuffer = opts.encoding === "buffer";
1944
2044
  const entry = {
1945
2045
  ns,
1946
2046
  absPath,
1947
2047
  recursive: opts.recursive ?? false,
1948
2048
  listener: cb,
1949
- signal
2049
+ signal,
2050
+ asBuffer,
2051
+ pendingEvents: null
1950
2052
  };
1951
2053
  ensureBc(ns);
1952
2054
  watchers.add(entry);
@@ -2090,6 +2192,7 @@ async function* watchAsync(ns, _asyncRequest, filePath, options) {
2090
2192
  const signal = options?.signal;
2091
2193
  const queue = [];
2092
2194
  let resolve2 = null;
2195
+ const asBuffer = options?.encoding === "buffer";
2093
2196
  const entry = {
2094
2197
  ns,
2095
2198
  absPath,
@@ -2101,7 +2204,9 @@ async function* watchAsync(ns, _asyncRequest, filePath, options) {
2101
2204
  resolve2 = null;
2102
2205
  }
2103
2206
  },
2104
- signal
2207
+ signal,
2208
+ asBuffer,
2209
+ pendingEvents: null
2105
2210
  };
2106
2211
  ensureBc(ns);
2107
2212
  watchers.add(entry);
@@ -2123,15 +2228,112 @@ async function* watchAsync(ns, _asyncRequest, filePath, options) {
2123
2228
  }
2124
2229
 
2125
2230
  // src/methods/glob.ts
2231
+ function expandBraces(pattern) {
2232
+ const out = [];
2233
+ function recurse(prefix, rest) {
2234
+ const open2 = findBrace(rest);
2235
+ if (open2 === -1) {
2236
+ out.push(prefix + rest);
2237
+ return;
2238
+ }
2239
+ const close = matchCloseBrace(rest, open2);
2240
+ if (close === -1) {
2241
+ out.push(prefix + rest);
2242
+ return;
2243
+ }
2244
+ const head = rest.slice(0, open2);
2245
+ const body = rest.slice(open2 + 1, close);
2246
+ const tail = rest.slice(close + 1);
2247
+ for (const alt of splitAlternations(body)) {
2248
+ recurse(prefix + head + alt, tail);
2249
+ }
2250
+ }
2251
+ recurse("", pattern);
2252
+ return out;
2253
+ }
2254
+ function findBrace(s) {
2255
+ for (let i = 0; i < s.length; i++) {
2256
+ const c = s[i];
2257
+ if (c === "\\") {
2258
+ i++;
2259
+ continue;
2260
+ }
2261
+ if (c === "[") {
2262
+ const end = s.indexOf("]", i + 1);
2263
+ if (end !== -1) {
2264
+ i = end;
2265
+ continue;
2266
+ }
2267
+ }
2268
+ if (c === "{") return i;
2269
+ }
2270
+ return -1;
2271
+ }
2272
+ function matchCloseBrace(s, open2) {
2273
+ let depth = 1;
2274
+ for (let i = open2 + 1; i < s.length; i++) {
2275
+ const c = s[i];
2276
+ if (c === "\\") {
2277
+ i++;
2278
+ continue;
2279
+ }
2280
+ if (c === "[") {
2281
+ const end = s.indexOf("]", i + 1);
2282
+ if (end !== -1) {
2283
+ i = end;
2284
+ continue;
2285
+ }
2286
+ }
2287
+ if (c === "{") depth++;
2288
+ else if (c === "}") {
2289
+ depth--;
2290
+ if (depth === 0) return i;
2291
+ }
2292
+ }
2293
+ return -1;
2294
+ }
2295
+ function splitAlternations(body) {
2296
+ const parts = [];
2297
+ let depth = 0;
2298
+ let start = 0;
2299
+ for (let i = 0; i < body.length; i++) {
2300
+ const c = body[i];
2301
+ if (c === "\\") {
2302
+ i++;
2303
+ continue;
2304
+ }
2305
+ if (c === "{") depth++;
2306
+ else if (c === "}") depth--;
2307
+ else if (c === "," && depth === 0) {
2308
+ parts.push(body.slice(start, i));
2309
+ start = i + 1;
2310
+ }
2311
+ }
2312
+ parts.push(body.slice(start));
2313
+ return parts;
2314
+ }
2126
2315
  function segmentToRegex(pattern) {
2127
2316
  let re = "^";
2128
2317
  for (let i = 0; i < pattern.length; i++) {
2129
2318
  const ch = pattern[i];
2130
- if (ch === "*") {
2319
+ if (ch === "\\" && i + 1 < pattern.length) {
2320
+ const next = pattern[++i];
2321
+ re += /[.+^${}()|[\]\\*?]/.test(next) ? "\\" + next : next;
2322
+ } else if (ch === "*") {
2131
2323
  re += "[^/]*";
2132
2324
  } else if (ch === "?") {
2133
2325
  re += "[^/]";
2134
- } else if (".+^${}()|[]\\".includes(ch)) {
2326
+ } else if (ch === "[") {
2327
+ const end = pattern.indexOf("]", i + 1);
2328
+ if (end === -1) {
2329
+ re += "\\[";
2330
+ } else {
2331
+ let body = pattern.slice(i + 1, end);
2332
+ if (body.startsWith("!")) body = "^" + body.slice(1);
2333
+ re += "[" + body + "]";
2334
+ i = end;
2335
+ }
2336
+ } else if (".+^${}()|\\".includes(ch)) {
2135
2337
  re += "\\" + ch;
2136
2338
  } else {
2137
2339
  re += ch;
@@ -2140,25 +2342,69 @@ function segmentToRegex(pattern) {
2140
2342
  re += "$";
2141
2343
  return new RegExp(re);
2142
2344
  }
2143
- function matchSegment(name, pattern) {
2144
- return segmentToRegex(pattern).test(name);
2145
- }
2146
2345
  function joinPath(base, name) {
2147
2346
  if (base === "/") return "/" + name;
2148
2347
  return base + "/" + name;
2149
2348
  }
2349
+ function normalizeCwd(cwd) {
2350
+ if (!cwd) return "/";
2351
+ if (typeof cwd === "string") return cwd || "/";
2352
+ return cwd.pathname || "/";
2353
+ }
2354
+ function makeDirent(parentPath, name, isDir, isSymlink) {
2355
+ return {
2356
+ name,
2357
+ parentPath,
2358
+ isFile: () => !isDir && !isSymlink,
2359
+ isDirectory: () => isDir,
2360
+ isBlockDevice: () => false,
2361
+ isCharacterDevice: () => false,
2362
+ isSymbolicLink: () => isSymlink,
2363
+ isFIFO: () => false,
2364
+ isSocket: () => false
2365
+ };
2366
+ }
2150
2367
  function globSync(syncRequest, pattern, options) {
2151
- const cwd = options?.cwd ?? "/";
2368
+ const patterns = Array.isArray(pattern) ? pattern : [pattern];
2369
+ const cwd = normalizeCwd(options?.cwd);
2152
2370
  const exclude = options?.exclude;
2153
- const segments = pattern.split("/").filter((s) => s !== "");
2154
- const results = [];
2155
- function walk(dir, segIdx) {
2371
+ const withFileTypes = options?.withFileTypes === true;
2372
+ const resultsSet = /* @__PURE__ */ new Set();
2373
+ const resultsDirents = [];
2374
+ const pushResult = (fullPath) => {
2375
+ if (withFileTypes) {
2376
+ if (!resultsSet.has(fullPath)) {
2377
+ resultsSet.add(fullPath);
2378
+ let isDir = false, isSymlink = false;
2379
+ try {
2380
+ const s = statSync(syncRequest, fullPath);
2381
+ isDir = s.isDirectory();
2382
+ } catch {
2383
+ }
2384
+ const slash = fullPath.lastIndexOf("/");
2385
+ const parent = slash <= 0 ? "/" : fullPath.slice(0, slash);
2386
+ const name = fullPath.slice(slash + 1);
2387
+ const dirent = makeDirent(parent, name, isDir, isSymlink);
2388
+ if (exclude && exclude(dirent)) {
2389
+ resultsSet.delete(fullPath);
2390
+ return;
2391
+ }
2392
+ resultsDirents.push(dirent);
2393
+ }
2394
+ } else {
2395
+ if (exclude && exclude(fullPath)) return;
2396
+ resultsSet.add(fullPath);
2397
+ }
2398
+ };
2399
+ function walk(dir, segments, segIdx) {
2156
2400
  if (segIdx >= segments.length) return;
2157
2401
  const seg = segments[segIdx];
2158
2402
  const isLast = segIdx === segments.length - 1;
2159
2403
  if (seg === "**") {
2160
2404
  if (segIdx + 1 < segments.length) {
2161
- walk(dir, segIdx + 1);
2405
+ walk(dir, segments, segIdx + 1);
2406
+ } else {
2407
+ pushResult(dir);
2162
2408
  }
2163
2409
  let entries2;
2164
2410
  try {
@@ -2168,20 +2414,16 @@ function globSync(syncRequest, pattern, options) {
2168
2414
  }
2169
2415
  for (const entry of entries2) {
2170
2416
  const full = joinPath(dir, entry);
2171
- if (exclude && exclude(full)) continue;
2172
2417
  let isDir;
2173
2418
  try {
2174
- const s = statSync(syncRequest, full);
2175
- isDir = s.isDirectory();
2419
+ isDir = statSync(syncRequest, full).isDirectory();
2176
2420
  } catch {
2177
2421
  continue;
2178
2422
  }
2179
2423
  if (isDir) {
2180
- walk(full, segIdx);
2181
- }
2182
- if (isLast) {
2183
- results.push(full);
2424
+ walk(full, segments, segIdx);
2184
2425
  }
2426
+ if (isLast) pushResult(full);
2185
2427
  }
2186
2428
  return;
2187
2429
  }
@@ -2191,41 +2433,71 @@ function globSync(syncRequest, pattern, options) {
2191
2433
  } catch {
2192
2434
  return;
2193
2435
  }
2436
+ const re = segmentToRegex(seg);
2194
2437
  for (const entry of entries) {
2195
- if (!matchSegment(entry, seg)) continue;
2438
+ if (!re.test(entry)) continue;
2196
2439
  const full = joinPath(dir, entry);
2197
- if (exclude && exclude(full)) continue;
2198
2440
  if (isLast) {
2199
- results.push(full);
2441
+ pushResult(full);
2200
2442
  } else {
2201
2443
  let isDir;
2202
2444
  try {
2203
- const s = statSync(syncRequest, full);
2204
- isDir = s.isDirectory();
2445
+ isDir = statSync(syncRequest, full).isDirectory();
2205
2446
  } catch {
2206
2447
  continue;
2207
2448
  }
2208
- if (isDir) {
2209
- walk(full, segIdx + 1);
2210
- }
2449
+ if (isDir) walk(full, segments, segIdx + 1);
2211
2450
  }
2212
2451
  }
2213
2452
  }
2214
- walk(cwd, 0);
2215
- return results;
2453
+ for (const pat of patterns) {
2454
+ for (const expanded of expandBraces(pat)) {
2455
+ const segments = expanded.split("/").filter((s) => s !== "");
2456
+ walk(cwd, segments, 0);
2457
+ }
2458
+ }
2459
+ return withFileTypes ? resultsDirents : Array.from(resultsSet);
2216
2460
  }
2217
2461
  async function glob(asyncRequest, pattern, options) {
2218
- const cwd = options?.cwd ?? "/";
2462
+ const patterns = Array.isArray(pattern) ? pattern : [pattern];
2463
+ const cwd = normalizeCwd(options?.cwd);
2219
2464
  const exclude = options?.exclude;
2220
- const segments = pattern.split("/").filter((s) => s !== "");
2221
- const results = [];
2222
- async function walk(dir, segIdx) {
2465
+ const withFileTypes = options?.withFileTypes === true;
2466
+ const resultsSet = /* @__PURE__ */ new Set();
2467
+ const resultsDirents = [];
2468
+ const pushResult = async (fullPath) => {
2469
+ if (withFileTypes) {
2470
+ if (resultsSet.has(fullPath)) return;
2471
+ resultsSet.add(fullPath);
2472
+ let isDir = false, isSymlink = false;
2473
+ try {
2474
+ const s = await stat(asyncRequest, fullPath);
2475
+ isDir = s.isDirectory();
2476
+ } catch {
2477
+ }
2478
+ const slash = fullPath.lastIndexOf("/");
2479
+ const parent = slash <= 0 ? "/" : fullPath.slice(0, slash);
2480
+ const name = fullPath.slice(slash + 1);
2481
+ const dirent = makeDirent(parent, name, isDir, isSymlink);
2482
+ if (exclude && exclude(dirent)) {
2483
+ resultsSet.delete(fullPath);
2484
+ return;
2485
+ }
2486
+ resultsDirents.push(dirent);
2487
+ } else {
2488
+ if (exclude && exclude(fullPath)) return;
2489
+ resultsSet.add(fullPath);
2490
+ }
2491
+ };
2492
+ async function walk(dir, segments, segIdx) {
2223
2493
  if (segIdx >= segments.length) return;
2224
2494
  const seg = segments[segIdx];
2225
2495
  const isLast = segIdx === segments.length - 1;
2226
2496
  if (seg === "**") {
2227
2497
  if (segIdx + 1 < segments.length) {
2228
- await walk(dir, segIdx + 1);
2498
+ await walk(dir, segments, segIdx + 1);
2499
+ } else {
2500
+ await pushResult(dir);
2229
2501
  }
2230
2502
  let entries2;
2231
2503
  try {
@@ -2235,20 +2507,14 @@ async function glob(asyncRequest, pattern, options) {
2235
2507
  }
2236
2508
  for (const entry of entries2) {
2237
2509
  const full = joinPath(dir, entry);
2238
- if (exclude && exclude(full)) continue;
2239
2510
  let isDir;
2240
2511
  try {
2241
- const s = await stat(asyncRequest, full);
2242
- isDir = s.isDirectory();
2512
+ isDir = (await stat(asyncRequest, full)).isDirectory();
2243
2513
  } catch {
2244
2514
  continue;
2245
2515
  }
2246
- if (isDir) {
2247
- await walk(full, segIdx);
2248
- }
2249
- if (isLast) {
2250
- results.push(full);
2251
- }
2516
+ if (isDir) await walk(full, segments, segIdx);
2517
+ if (isLast) await pushResult(full);
2252
2518
  }
2253
2519
  return;
2254
2520
  }
@@ -2258,28 +2524,30 @@ async function glob(asyncRequest, pattern, options) {
2258
2524
  } catch {
2259
2525
  return;
2260
2526
  }
2527
+ const re = segmentToRegex(seg);
2261
2528
  for (const entry of entries) {
2262
- if (!matchSegment(entry, seg)) continue;
2529
+ if (!re.test(entry)) continue;
2263
2530
  const full = joinPath(dir, entry);
2264
- if (exclude && exclude(full)) continue;
2265
2531
  if (isLast) {
2266
- results.push(full);
2532
+ await pushResult(full);
2267
2533
  } else {
2268
2534
  let isDir;
2269
2535
  try {
2270
- const s = await stat(asyncRequest, full);
2271
- isDir = s.isDirectory();
2536
+ isDir = (await stat(asyncRequest, full)).isDirectory();
2272
2537
  } catch {
2273
2538
  continue;
2274
2539
  }
2275
- if (isDir) {
2276
- await walk(full, segIdx + 1);
2277
- }
2540
+ if (isDir) await walk(full, segments, segIdx + 1);
2278
2541
  }
2279
2542
  }
2280
2543
  }
2281
- await walk(cwd, 0);
2282
- return results;
2544
+ for (const pat of patterns) {
2545
+ for (const expanded of expandBraces(pat)) {
2546
+ const segments = expanded.split("/").filter((s) => s !== "");
2547
+ await walk(cwd, segments, 0);
2548
+ }
2549
+ }
2550
+ return withFileTypes ? resultsDirents : Array.from(resultsSet);
2283
2551
  }
2284
2552
 
2285
2553
  // src/filesystem.ts
@@ -3007,8 +3275,11 @@ var VFSFileSystem = class {
3007
3275
  lchmodSync(filePath, mode) {
3008
3276
  chmodSync(this._sync, filePath, mode);
3009
3277
  }
3010
- /** chmod on an open file descriptor. No-op in this VFS (permissions are cosmetic). */
3011
- fchmodSync(_fd, _mode) {
3278
+ /** chmod on an open file descriptor. Resolves the fd to its inode on the
3279
+ * server side and mutates the inode's mode bits directly, matching what
3280
+ * native Node's libuv does. */
3281
+ fchmodSync(fd, mode) {
3282
+ fchmodSync(this._sync, fd, mode);
3012
3283
  }
3013
3284
  chownSync(filePath, uid, gid) {
3014
3285
  chownSync(this._sync, toPathString(filePath), uid, gid);
@@ -3017,14 +3288,16 @@ var VFSFileSystem = class {
3017
3288
  lchownSync(filePath, uid, gid) {
3018
3289
  chownSync(this._sync, filePath, uid, gid);
3019
3290
  }
3020
- /** chown on an open file descriptor. No-op in this VFS (permissions are cosmetic). */
3021
- fchownSync(_fd, _uid, _gid) {
3291
+ /** chown on an open file descriptor. Mutates the underlying inode's uid/gid. */
3292
+ fchownSync(fd, uid, gid) {
3293
+ fchownSync(this._sync, fd, uid, gid);
3022
3294
  }
3023
3295
  utimesSync(filePath, atime, mtime) {
3024
3296
  utimesSync(this._sync, toPathString(filePath), atime, mtime);
3025
3297
  }
3026
- /** utimes on an open file descriptor. No-op in this VFS (cannot resolve fd to path). */
3027
- futimesSync(_fd, _atime, _mtime) {
3298
+ /** utimes on an open file descriptor. Mutates the underlying inode's atime/mtime. */
3299
+ futimesSync(fd, atime, mtime) {
3300
+ futimesSync(this._sync, fd, atime, mtime);
3028
3301
  }
3029
3302
  /** Like utimesSync but operates on the symlink itself. In this VFS, delegates to utimesSync. */
3030
3303
  lutimesSync(filePath, atime, mtime) {
@@ -3649,15 +3922,15 @@ var VFSFileSystem = class {
3649
3922
  }
3650
3923
  futimes(fd, atime, mtime, callback) {
3651
3924
  this._validateCb(callback);
3652
- if (callback) setTimeout(() => callback(null), 0);
3925
+ return this._cbVoid(this.promises.futimes(fd, atime, mtime), callback);
3653
3926
  }
3654
3927
  fchmod(fd, mode, callback) {
3655
3928
  this._validateCb(callback);
3656
- if (callback) setTimeout(() => callback(null), 0);
3929
+ return this._cbVoid(this.promises.fchmod(fd, mode), callback);
3657
3930
  }
3658
3931
  fchown(fd, uid, gid, callback) {
3659
3932
  this._validateCb(callback);
3660
- if (callback) setTimeout(() => callback(null), 0);
3933
+ return this._cbVoid(this.promises.fchown(fd, uid, gid), callback);
3661
3934
  }
3662
3935
  lchmod(filePath, mode, callback) {
3663
3936
  this._validateCb(callback);
@@ -3796,8 +4069,10 @@ var VFSPromises = class {
3796
4069
  lchmod(filePath, mode) {
3797
4070
  return chmod(this._async, filePath, mode);
3798
4071
  }
3799
- /** chmod on an open file descriptor. No-op in this VFS (permissions are cosmetic). */
3800
- async fchmod(_fd, _mode) {
4072
+ /** chmod on an open file descriptor. Engine resolves fd inode and
4073
+ * mutates the mode bits directly. */
4074
+ fchmod(fd, mode) {
4075
+ return fchmod(this._async, fd, mode);
3801
4076
  }
3802
4077
  chown(filePath, uid, gid) {
3803
4078
  return chown(this._async, toPathString(filePath), uid, gid);
@@ -3806,14 +4081,18 @@ var VFSPromises = class {
3806
4081
  lchown(filePath, uid, gid) {
3807
4082
  return chown(this._async, filePath, uid, gid);
3808
4083
  }
3809
- /** chown on an open file descriptor. No-op in this VFS (permissions are cosmetic). */
3810
- async fchown(_fd, _uid, _gid) {
4084
+ /** chown on an open file descriptor. Engine resolves fd inode and
4085
+ * mutates uid/gid directly. */
4086
+ fchown(fd, uid, gid) {
4087
+ return fchown(this._async, fd, uid, gid);
3811
4088
  }
3812
4089
  utimes(filePath, atime, mtime) {
3813
4090
  return utimes(this._async, toPathString(filePath), atime, mtime);
3814
4091
  }
3815
- /** utimes on an open file descriptor. No-op in this VFS (cannot resolve fd to path). */
3816
- async futimes(_fd, _atime, _mtime) {
4092
+ /** utimes on an open file descriptor. Engine resolves fd inode and
4093
+ * mutates atime/mtime directly. */
4094
+ futimes(fd, atime, mtime) {
4095
+ return futimes(this._async, fd, atime, mtime);
3817
4096
  }
3818
4097
  /** Like utimes but operates on the symlink itself. In this VFS, delegates to utimes. */
3819
4098
  lutimes(filePath, atime, mtime) {
@@ -5133,6 +5412,40 @@ var VFSEngine = class {
5133
5412
  this.handle.flush();
5134
5413
  return { status: 0 };
5135
5414
  }
5415
+ // ---- FCHMOD ----
5416
+ // fd-based chmod: look up the inode directly from the fd table and mutate
5417
+ // its mode bits. Native Node does the same thing at the libuv layer.
5418
+ fchmod(fd, mode) {
5419
+ const entry = this.fdTable.get(fd);
5420
+ if (!entry) return { status: CODE_TO_STATUS.EBADF };
5421
+ const inode = this.readInode(entry.inodeIdx);
5422
+ inode.mode = inode.mode & S_IFMT | mode & 4095;
5423
+ inode.ctime = Date.now();
5424
+ this.writeInode(entry.inodeIdx, inode);
5425
+ return { status: 0 };
5426
+ }
5427
+ // ---- FCHOWN ----
5428
+ fchown(fd, uid, gid) {
5429
+ const entry = this.fdTable.get(fd);
5430
+ if (!entry) return { status: CODE_TO_STATUS.EBADF };
5431
+ const inode = this.readInode(entry.inodeIdx);
5432
+ inode.uid = uid;
5433
+ inode.gid = gid;
5434
+ inode.ctime = Date.now();
5435
+ this.writeInode(entry.inodeIdx, inode);
5436
+ return { status: 0 };
5437
+ }
5438
+ // ---- FUTIMES ----
5439
+ futimes(fd, atime, mtime) {
5440
+ const entry = this.fdTable.get(fd);
5441
+ if (!entry) return { status: CODE_TO_STATUS.EBADF };
5442
+ const inode = this.readInode(entry.inodeIdx);
5443
+ inode.atime = atime;
5444
+ inode.mtime = mtime;
5445
+ inode.ctime = Date.now();
5446
+ this.writeInode(entry.inodeIdx, inode);
5447
+ return { status: 0 };
5448
+ }
5136
5449
  // ---- OPENDIR ----
5137
5450
  opendir(path, tabId) {
5138
5451
  path = this.normalizePath(path);