@zenfs/core 2.5.1 → 2.5.2

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.
@@ -898,25 +898,33 @@ export async function statfs(path, opts) {
898
898
  export function glob(pattern, opt) {
899
899
  pattern = Array.isArray(pattern) ? pattern : [pattern];
900
900
  const { cwd = '/', withFileTypes = false, exclude = () => false } = opt || {};
901
- // Escape special characters in pattern
902
- const regexPatterns = pattern.map(globToRegex);
901
+ const normalizedPatterns = pattern.map(p => p.replace(/^\/+/g, ''));
902
+ const hasGlobStar = normalizedPatterns.some(p => p.includes('**'));
903
+ const patternBases = normalizedPatterns.map(p => {
904
+ const firstGlob = p.search(/[*?[\]{]/);
905
+ if (firstGlob === -1)
906
+ return p;
907
+ const lastSlash = p.lastIndexOf('/', firstGlob);
908
+ return lastSlash === -1 ? '' : p.slice(0, lastSlash);
909
+ });
910
+ const regexPatterns = normalizedPatterns.map(globToRegex);
903
911
  async function* recursiveList(dir) {
904
912
  const entries = await readdir(dir, { withFileTypes, encoding: 'utf8' });
905
913
  for (const entry of entries) {
906
- const fullPath = withFileTypes ? join(entry.parentPath, entry.name) : dir + '/' + entry;
914
+ const fullPath = join(dir, withFileTypes ? entry.name : entry);
907
915
  if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, fullPath)) : exclude((withFileTypes ? entry : fullPath)))
908
916
  continue;
909
- /**
910
- * @todo is the pattern.source check correct?
911
- */
912
- if ((await stat(fullPath)).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) {
913
- yield* recursiveList(fullPath);
917
+ const relativePath = fullPath.replace(/^\/+/g, '');
918
+ if ((await stat(fullPath)).isDirectory()) {
919
+ if (hasGlobStar || patternBases.some(base => relativePath === base || base.startsWith(relativePath + '/'))) {
920
+ yield* recursiveList(fullPath);
921
+ }
914
922
  }
915
- if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) {
916
- yield withFileTypes ? entry : fullPath.replace(/^\/+/g, '');
923
+ if (regexPatterns.some(rx => rx.test(relativePath))) {
924
+ yield withFileTypes ? entry : relativePath;
917
925
  }
918
926
  }
919
927
  }
920
- return recursiveList(cwd);
928
+ return recursiveList(cwd instanceof URL ? cwd.pathname : cwd);
921
929
  }
922
930
  glob;
package/dist/node/sync.js CHANGED
@@ -656,27 +656,35 @@ export function statfsSync(path, options) {
656
656
  export function globSync(pattern, options = {}) {
657
657
  pattern = Array.isArray(pattern) ? pattern : [pattern];
658
658
  const { cwd = '/', withFileTypes = false, exclude = () => false } = options;
659
- // Escape special characters in pattern
660
- const regexPatterns = pattern.map(globToRegex);
659
+ const normalizedPatterns = pattern.map(p => p.replace(/^\/+/g, ''));
660
+ const hasGlobStar = normalizedPatterns.some(p => p.includes('**'));
661
+ const patternBases = normalizedPatterns.map(p => {
662
+ const firstGlob = p.search(/[*?[\]{]/);
663
+ if (firstGlob === -1)
664
+ return p;
665
+ const lastSlash = p.lastIndexOf('/', firstGlob);
666
+ return lastSlash === -1 ? '' : p.slice(0, lastSlash);
667
+ });
668
+ const regexPatterns = normalizedPatterns.map(globToRegex);
661
669
  const results = [];
662
670
  function recursiveList(dir) {
663
671
  const entries = readdirSync(dir, { withFileTypes, encoding: 'utf8' });
664
672
  for (const entry of entries) {
665
- const fullPath = withFileTypes ? join(entry.parentPath, entry.name) : dir + '/' + entry;
673
+ const fullPath = join(dir, withFileTypes ? entry.name : entry);
666
674
  if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, fullPath)) : exclude((withFileTypes ? entry : fullPath)))
667
675
  continue;
668
- /**
669
- * @todo is the pattern.source check correct?
670
- */
671
- if (statSync(fullPath).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) {
672
- recursiveList(fullPath);
676
+ const relativePath = fullPath.replace(/^\/+/g, '');
677
+ if (statSync(fullPath).isDirectory()) {
678
+ if (hasGlobStar || patternBases.some(base => relativePath === base || base.startsWith(relativePath + '/'))) {
679
+ recursiveList(fullPath);
680
+ }
673
681
  }
674
- if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) {
675
- results.push(withFileTypes ? entry : fullPath.replace(/^\/+/g, ''));
682
+ if (regexPatterns.some(rx => rx.test(relativePath))) {
683
+ results.push(withFileTypes ? entry : relativePath);
676
684
  }
677
685
  }
678
686
  }
679
- recursiveList(cwd);
687
+ recursiveList(cwd instanceof URL ? cwd.pathname : cwd);
680
688
  return results;
681
689
  }
682
690
  globSync;
package/dist/utils.js CHANGED
@@ -97,11 +97,16 @@ export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) {
97
97
  * @internal
98
98
  */
99
99
  export function globToRegex(pattern) {
100
+ const GLOBSTAR = '\0GS\0';
101
+ const STAR = '\0S\0';
100
102
  pattern = pattern
101
- .replace(/([.?+^$(){}|[\]/])/g, '$1')
102
- .replace(/\*\*/g, '.*')
103
- .replace(/\*/g, '[^/]*')
104
- .replace(/\?/g, '.');
103
+ .replace(/\*\*/g, GLOBSTAR)
104
+ .replace(/\*/g, STAR)
105
+ .replace(/[.+^$(){}|[\]\\]/g, '\\$&')
106
+ .replace(/\?/g, '.')
107
+ .replaceAll('/' + GLOBSTAR + '/', '(?:/.*)?/')
108
+ .replaceAll(GLOBSTAR, '.*')
109
+ .replaceAll(STAR, '[^/]*');
105
110
  return new RegExp(`^${pattern}$`);
106
111
  }
107
112
  export async function waitOnline(worker) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -0,0 +1,135 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import assert from 'node:assert/strict';
3
+ import { suite, test } from 'node:test';
4
+ import { fs } from '../common.js';
5
+
6
+ // Set up a directory structure for glob tests
7
+ fs.mkdirSync('/glob');
8
+ fs.mkdirSync('/glob/sub');
9
+ fs.mkdirSync('/glob/sub/deep');
10
+ fs.writeFileSync('/glob/a.txt', 'a');
11
+ fs.writeFileSync('/glob/b.txt', 'b');
12
+ fs.writeFileSync('/glob/c.js', 'c');
13
+ fs.writeFileSync('/glob/sub/d.txt', 'd');
14
+ fs.writeFileSync('/glob/sub/e.js', 'e');
15
+ fs.writeFileSync('/glob/sub/deep/f.txt', 'f');
16
+
17
+ suite('globSync', () => {
18
+ test('wildcard in root', () => {
19
+ const results = fs.globSync('glob/*');
20
+ assert(results.includes('glob/a.txt'), 'should include glob/a.txt');
21
+ assert(results.includes('glob/b.txt'), 'should include glob/b.txt');
22
+ assert(results.includes('glob/c.js'), 'should include glob/c.js');
23
+ assert(results.includes('glob/sub'), 'should include glob/sub');
24
+ });
25
+
26
+ test('wildcard with absolute path pattern', () => {
27
+ const results = fs.globSync('/glob/*');
28
+ assert(results.includes('glob/a.txt'), 'should include glob/a.txt');
29
+ assert(results.includes('glob/b.txt'), 'should include glob/b.txt');
30
+ assert(results.includes('glob/c.js'), 'should include glob/c.js');
31
+ });
32
+
33
+ test('wildcard with extension filter', () => {
34
+ const results = fs.globSync('/glob/*.txt');
35
+ assert(results.includes('glob/a.txt'));
36
+ assert(results.includes('glob/b.txt'));
37
+ assert(!results.includes('glob/c.js'), 'should not include .js files');
38
+ });
39
+
40
+ test('nested path wildcard', () => {
41
+ const results = fs.globSync('/glob/sub/*');
42
+ assert(results.includes('glob/sub/d.txt'));
43
+ assert(results.includes('glob/sub/e.js'));
44
+ assert(!results.includes('glob/a.txt'), 'should not include files from parent');
45
+ });
46
+
47
+ test('globstar (**)', () => {
48
+ const results = fs.globSync('/glob/**/*.txt');
49
+ assert(results.includes('glob/a.txt'));
50
+ assert(results.includes('glob/b.txt'));
51
+ assert(results.includes('glob/sub/d.txt'));
52
+ assert(results.includes('glob/sub/deep/f.txt'));
53
+ assert(!results.includes('glob/c.js'), 'should not include .js files');
54
+ });
55
+
56
+ test('question mark wildcard', () => {
57
+ const results = fs.globSync('/glob/?.txt');
58
+ assert(results.includes('glob/a.txt'));
59
+ assert(results.includes('glob/b.txt'));
60
+ assert(!results.includes('glob/c.js'));
61
+ });
62
+
63
+ test('multiple patterns', () => {
64
+ const results = fs.globSync(['/glob/*.txt', '/glob/*.js']);
65
+ assert(results.includes('glob/a.txt'));
66
+ assert(results.includes('glob/c.js'));
67
+ });
68
+
69
+ test('no matches returns empty', () => {
70
+ const results = fs.globSync('/glob/*.xyz');
71
+ assert.equal(results.length, 0);
72
+ });
73
+
74
+ test('withFileTypes option', () => {
75
+ const results = fs.globSync('/glob/*.txt', { withFileTypes: true });
76
+ assert(results.length > 0, 'should have results');
77
+ assert(typeof results[0] === 'object' && 'name' in results[0], 'results should be Dirent objects');
78
+ });
79
+
80
+ test('exclude option with function', () => {
81
+ const results = fs.globSync('/glob/*', { exclude: path => typeof path === 'string' && path.endsWith('.js') });
82
+ assert(!results.includes('glob/c.js'), 'should exclude .js files');
83
+ assert(results.includes('glob/a.txt'), 'should still include .txt files');
84
+ });
85
+ });
86
+
87
+ await suite('promises.glob', () => {
88
+ test('wildcard in root', async () => {
89
+ const results = await Array.fromAsync(fs.promises.glob('/glob/*'));
90
+ assert(results.includes('glob/a.txt'));
91
+ assert(results.includes('glob/b.txt'));
92
+ assert(results.includes('glob/c.js'));
93
+ assert(results.includes('glob/sub'));
94
+ });
95
+
96
+ test('wildcard with absolute path pattern', async () => {
97
+ const results = await Array.fromAsync(fs.promises.glob('/glob/*'));
98
+ assert(results.includes('glob/a.txt'));
99
+ assert(results.includes('glob/b.txt'));
100
+ });
101
+
102
+ test('wildcard with extension filter', async () => {
103
+ const results = await Array.fromAsync(fs.promises.glob('/glob/*.txt'));
104
+ assert(results.includes('glob/a.txt'));
105
+ assert(results.includes('glob/b.txt'));
106
+ assert(!results.includes('glob/c.js'));
107
+ });
108
+
109
+ test('nested path wildcard', async () => {
110
+ const results = await Array.fromAsync(fs.promises.glob('/glob/sub/*'));
111
+ assert(results.includes('glob/sub/d.txt'));
112
+ assert(results.includes('glob/sub/e.js'));
113
+ assert(!results.includes('glob/a.txt'));
114
+ });
115
+
116
+ test('globstar (**)', async () => {
117
+ const results = await Array.fromAsync(fs.promises.glob('/glob/**/*.txt'));
118
+ assert(results.includes('glob/a.txt'));
119
+ assert(results.includes('glob/b.txt'));
120
+ assert(results.includes('glob/sub/d.txt'));
121
+ assert(results.includes('glob/sub/deep/f.txt'));
122
+ assert(!results.includes('glob/c.js'));
123
+ });
124
+
125
+ test('multiple patterns', async () => {
126
+ const results = await Array.fromAsync(fs.promises.glob(['/glob/*.txt', '/glob/*.js']));
127
+ assert(results.includes('glob/a.txt'));
128
+ assert(results.includes('glob/c.js'));
129
+ });
130
+
131
+ test('no matches returns empty', async () => {
132
+ const results = await Array.fromAsync(fs.promises.glob('/glob/*.xyz'));
133
+ assert.equal(results.length, 0);
134
+ });
135
+ });