filehound 2.0.0-beta.2 → 2.0.0-beta.3
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/README.md +37 -11
- package/dist/arrays.d.ts +1 -0
- package/dist/arrays.js +4 -0
- package/dist/file.d.ts +108 -0
- package/dist/file.js +184 -0
- package/dist/filehound.d.ts +322 -0
- package/dist/filehound.js +443 -0
- package/dist/files.d.ts +4 -0
- package/dist/files.js +37 -0
- package/dist/filetype.d.ts +10 -0
- package/dist/filetype.js +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/matchers.d.ts +297 -0
- package/dist/matchers.js +389 -0
- package/dist/walker/async.d.ts +4 -0
- package/dist/walker/async.js +237 -0
- package/dist/walker/heap.d.ts +7 -0
- package/dist/walker/heap.js +49 -0
- package/dist/walker/sync.d.ts +4 -0
- package/dist/walker/sync.js +66 -0
- package/dist/walker/walk.d.ts +32 -0
- package/dist/walker/walk.js +70 -0
- package/package.json +44 -48
- package/.github/stale.yml +0 -42
- package/.travis.yml +0 -8
- package/.vscode/launch.json +0 -21
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -8
- package/_config.yml +0 -1
- package/coverage/coverage.raw.json +0 -1
- package/coverage/lcov-report/arrays.ts.html +0 -98
- package/coverage/lcov-report/base.css +0 -212
- package/coverage/lcov-report/filehound.ts.html +0 -2078
- package/coverage/lcov-report/files.ts.html +0 -260
- package/coverage/lcov-report/functions.ts.html +0 -122
- package/coverage/lcov-report/index.html +0 -132
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -1
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -158
- package/coverage/lcov.info +0 -378
- package/dist/src/arrays.js +0 -15
- package/dist/src/arrays.js.map +0 -1
- package/dist/src/asyncWalker.js +0 -59
- package/dist/src/asyncWalker.js.map +0 -1
- package/dist/src/bind.js +0 -15
- package/dist/src/bind.js.map +0 -1
- package/dist/src/filehound.js +0 -597
- package/dist/src/filehound.js.map +0 -1
- package/dist/src/files.js +0 -60
- package/dist/src/files.js.map +0 -1
- package/dist/src/matcher.js +0 -51
- package/dist/src/matcher.js.map +0 -1
- package/dist/src/syncWalker.js +0 -47
- package/dist/src/syncWalker.js.map +0 -1
- package/dist/src/walkAsync.js +0 -33
- package/dist/src/walkAsync.js.map +0 -1
- package/dist/src/walkSync.js +0 -21
- package/dist/src/walkSync.js.map +0 -1
- package/dist/src/walker/walk.js +0 -68
- package/dist/src/walker/walk.js.map +0 -1
- package/dist/src/walker/walkAsync.js +0 -33
- package/dist/src/walker/walkAsync.js.map +0 -1
- package/dist/src/walker/walkSync.js +0 -21
- package/dist/src/walker/walkSync.js.map +0 -1
- package/dist/test/filehound.js +0 -872
- package/dist/test/filehound.js.map +0 -1
- package/dist/test/files.js +0 -29
- package/dist/test/files.js.map +0 -1
- package/docconfig/jsdoc.json +0 -26
- package/docconfig/template/LICENSE +0 -61
- package/docconfig/template/README.md +0 -86
- package/docconfig/template/package.json +0 -22
- package/docconfig/template/publish.js +0 -676
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docconfig/template/static/scripts/linenumber.js +0 -25
- package/docconfig/template/static/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docconfig/template/static/scripts/prettify/lang-css.js +0 -2
- package/docconfig/template/static/scripts/prettify/prettify.js +0 -28
- package/docconfig/template/static/styles/jsdoc-default.css +0 -605
- package/docconfig/template/static/styles/prettify-jsdoc.css +0 -111
- package/docconfig/template/static/styles/prettify-tomorrow.css +0 -132
- package/docconfig/template/tmpl/augments.tmpl +0 -10
- package/docconfig/template/tmpl/container.tmpl +0 -183
- package/docconfig/template/tmpl/details.tmpl +0 -143
- package/docconfig/template/tmpl/example.tmpl +0 -2
- package/docconfig/template/tmpl/examples.tmpl +0 -13
- package/docconfig/template/tmpl/exceptions.tmpl +0 -32
- package/docconfig/template/tmpl/layout.tmpl +0 -47
- package/docconfig/template/tmpl/mainpage.tmpl +0 -10
- package/docconfig/template/tmpl/members.tmpl +0 -38
- package/docconfig/template/tmpl/method.tmpl +0 -105
- package/docconfig/template/tmpl/params.tmpl +0 -124
- package/docconfig/template/tmpl/properties.tmpl +0 -108
- package/docconfig/template/tmpl/returns.tmpl +0 -19
- package/docconfig/template/tmpl/source.tmpl +0 -8
- package/docconfig/template/tmpl/tutorial.tmpl +0 -19
- package/docconfig/template/tmpl/type.tmpl +0 -7
- package/docs/FileHound.html +0 -3079
- package/docs/FileHound_modified.html +0 -245
- package/docs/_config.yml +0 -1
- package/docs/filehound.js.html +0 -697
- package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docs/global.html +0 -288
- package/docs/index.html +0 -154
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/styles/jsdoc-default.css +0 -605
- package/docs/styles/prettify-jsdoc.css +0 -111
- package/docs/styles/prettify-tomorrow.css +0 -132
- package/docs.md +0 -164
- package/index.js +0 -1
- package/issue_template.md +0 -6
- package/test/filehound.ts +0 -1076
- package/test/files.ts +0 -29
- package/test/fixtures/custom/failed.txt +0 -0
- package/test/fixtures/custom/passed.txt +0 -0
- package/test/fixtures/deeplyNested/c.json +0 -0
- package/test/fixtures/deeplyNested/d.json +0 -0
- package/test/fixtures/deeplyNested/mydir/e.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/f.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/z.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/y.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/c.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/d.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/e.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/f.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/.mydir5/.keep +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/z.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/y.json +0 -0
- package/test/fixtures/ext/Dummy.java +0 -0
- package/test/fixtures/ext/Dumpy.go +0 -0
- package/test/fixtures/ext/dummy.json +0 -0
- package/test/fixtures/ext/dummy.txt +0 -0
- package/test/fixtures/justFiles/a.json +0 -0
- package/test/fixtures/justFiles/b.json +0 -0
- package/test/fixtures/justFiles/dummy.txt +0 -0
- package/test/fixtures/mixed/a.json +0 -0
- package/test/fixtures/mixed/aabbcc.json +0 -0
- package/test/fixtures/mixed/ab.json +0 -0
- package/test/fixtures/mixed/z.json +0 -0
- package/test/fixtures/nested/.hidden1/bad.txt +0 -0
- package/test/fixtures/nested/c.json +0 -0
- package/test/fixtures/nested/d.json +0 -0
- package/test/fixtures/nested/mydir/e.json +0 -0
- package/test/fixtures/sizes/10b.txt +0 -0
- package/test/fixtures/sizes/1b.txt +0 -0
- package/test/fixtures/sizes/1k.txt +0 -0
- package/test/fixtures/sizes/2k.txt +0 -0
- package/test/fixtures/visibility/.hidden/.hidden.json +0 -0
- package/test/fixtures/visibility/.hidden/visible.json +0 -3
- package/test/fixtures/visibility/.hidden.json +0 -3
- package/test/fixtures/visibility/visible/.hidden.json +0 -3
- package/test/fixtures/visibility/visible.json +0 -3
- package/test/mocha.opts +0 -4
- package/tsconfig.json +0 -16
- package/tslint.json +0 -44
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as fsp from 'node:fs/promises';
|
|
2
|
+
import { File, getChildren, getKnownKind } from '../file.js';
|
|
3
|
+
import { Heap } from './heap.js';
|
|
4
|
+
import { ancestorsOf, assertDirectory, byName, createPathError, EntryType, typeOf, Walk } from './walk.js';
|
|
5
|
+
// How the async walk works, and why
|
|
6
|
+
//
|
|
7
|
+
// The walk yields matches in batches, in the same order as the sync walk:
|
|
8
|
+
// depth first, with siblings in name order. Whoever reads the batches sets the
|
|
9
|
+
// pace, so the walk can't run ahead without limit, and stops when they stop.
|
|
10
|
+
//
|
|
11
|
+
// Walking in order on its own is slow, as each directory would wait on its
|
|
12
|
+
// read. So work is started ahead of the walk, within two limits:
|
|
13
|
+
//
|
|
14
|
+
// - WINDOW: within a directory, the siblings after the one being walked are
|
|
15
|
+
// visited early, starting their stats, predicates and reads.
|
|
16
|
+
// - READ_AHEAD: across the tree, directories further on are read early.
|
|
17
|
+
//
|
|
18
|
+
// Directories are read ahead in the order the walk will reach them, using
|
|
19
|
+
// each one's Position and a heap. This order matters, and simpler orders were
|
|
20
|
+
// measured and found slow. Node queues reads, so a read the walk needs now
|
|
21
|
+
// waits behind every read started before it. Reading ahead among siblings only
|
|
22
|
+
// lost the concurrency of reading the tree at once (about 30% slower than
|
|
23
|
+
// reading it all, unbounded). Reading ahead in the order reads finished was
|
|
24
|
+
// worse: the walk's reads waited behind reads for far-off directories, and it
|
|
25
|
+
// was only fast once READ_AHEAD exceeded the number of directories in the tree.
|
|
26
|
+
// In walk order, a READ_AHEAD of 32 is enough to match unbounded reading.
|
|
27
|
+
//
|
|
28
|
+
// So before changing the order of reads, measure it with `npm run bench`, on
|
|
29
|
+
// trees both deep and wide.
|
|
30
|
+
//
|
|
31
|
+
// Work started ahead can fail, or be abandoned, before the walk reaches it.
|
|
32
|
+
// prefetch() stops those failures being reported as unhandled, and an aborted
|
|
33
|
+
// walk starts no new reads.
|
|
34
|
+
// How many siblings are visited ahead of the one being walked. Visiting starts
|
|
35
|
+
// the I/O an entry needs: a directory's read, a link's stat or a predicate's.
|
|
36
|
+
// Each level of the walk has its own window, so the work in flight is bounded
|
|
37
|
+
// by this times the depth.
|
|
38
|
+
const WINDOW = 64;
|
|
39
|
+
// How many more directories can be read ahead of the walk, beyond those in
|
|
40
|
+
// its windows, and not yet walked
|
|
41
|
+
const READ_AHEAD = 64;
|
|
42
|
+
// Work started ahead of the walk may fail before the walk reaches it, and
|
|
43
|
+
// later awaits still see the failure. Until then, Node would otherwise report
|
|
44
|
+
// it as unhandled.
|
|
45
|
+
function prefetch(pending) {
|
|
46
|
+
pending.catch(() => { });
|
|
47
|
+
return pending;
|
|
48
|
+
}
|
|
49
|
+
// Compared index by index, positions give walk order: depth first, in name order
|
|
50
|
+
function precedes(a, b) {
|
|
51
|
+
for (let i = 0; i < a.length && i < b.length; i++) {
|
|
52
|
+
if (a[i] !== b[i]) {
|
|
53
|
+
return a[i] < b[i];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return a.length < b.length;
|
|
57
|
+
}
|
|
58
|
+
// Reading directories is most of the work of a walk, so they are read ahead
|
|
59
|
+
// of it, in the order the walk will reach them. Reads are queued, so one read
|
|
60
|
+
// out of order would hold up the walk behind the rest.
|
|
61
|
+
class AsyncWalk extends Walk {
|
|
62
|
+
#signal;
|
|
63
|
+
// Read or being read, and not yet taken by the walk
|
|
64
|
+
#reads = new Map();
|
|
65
|
+
// Found but not yet read
|
|
66
|
+
#found = new Heap((a, b) => precedes(a.position, b.position));
|
|
67
|
+
// Of directories, and links that may be, for when the walk reads them
|
|
68
|
+
#positions = new WeakMap();
|
|
69
|
+
#taken = new WeakSet();
|
|
70
|
+
constructor(matcher, opts) {
|
|
71
|
+
super(matcher, opts);
|
|
72
|
+
this.#signal = opts.signal;
|
|
73
|
+
}
|
|
74
|
+
// Rejects as soon as the walk is aborted, rather than when the work is done,
|
|
75
|
+
// which for a slow predicate could be a long wait
|
|
76
|
+
abortable(pending) {
|
|
77
|
+
const signal = this.#signal;
|
|
78
|
+
if (!signal) {
|
|
79
|
+
return pending;
|
|
80
|
+
}
|
|
81
|
+
signal.throwIfAborted();
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const abort = () => reject(signal.reason);
|
|
84
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
85
|
+
pending.then((value) => {
|
|
86
|
+
signal.removeEventListener('abort', abort);
|
|
87
|
+
resolve(value);
|
|
88
|
+
}, (error) => {
|
|
89
|
+
signal.removeEventListener('abort', abort);
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// For after a yield, as whoever reads the walk may have aborted it meanwhile
|
|
95
|
+
throwIfAborted() {
|
|
96
|
+
this.#signal?.throwIfAborted();
|
|
97
|
+
}
|
|
98
|
+
// The directory's children in name order, read ahead if it was reached
|
|
99
|
+
read(dir, depth) {
|
|
100
|
+
this.#taken.add(dir);
|
|
101
|
+
const read = this.#reads.get(dir) ?? this.#start({ dir, depth, position: this.#positions.get(dir) ?? [] });
|
|
102
|
+
this.#reads.delete(dir);
|
|
103
|
+
this.#readAhead();
|
|
104
|
+
return read;
|
|
105
|
+
}
|
|
106
|
+
#start({ dir, depth, position }) {
|
|
107
|
+
return prefetch(getChildren(dir).then((children) => {
|
|
108
|
+
children.sort(byName);
|
|
109
|
+
this.#find(children, depth + 1, position);
|
|
110
|
+
return children;
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
// Links are left to the walk, which needs a stat to know if they're directories
|
|
114
|
+
#find(children, depth, parent) {
|
|
115
|
+
if (!this.canDescend(depth)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
children.forEach((child, i) => {
|
|
119
|
+
const kind = getKnownKind(child);
|
|
120
|
+
if (kind && !kind.isDirectory()) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const position = [...parent, i];
|
|
124
|
+
this.#positions.set(child, position);
|
|
125
|
+
if (kind && !this.shouldSkipDirectory(child)) {
|
|
126
|
+
this.#found.push({ dir: child, depth, position });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
this.#readAhead();
|
|
130
|
+
}
|
|
131
|
+
#readAhead() {
|
|
132
|
+
if (this.#signal?.aborted) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
while (this.#reads.size < READ_AHEAD && this.#found.size > 0) {
|
|
136
|
+
const found = this.#found.pop();
|
|
137
|
+
if (!this.#taken.has(found.dir)) {
|
|
138
|
+
this.#reads.set(found.dir, this.#start(found));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// A linked directory that resolves to one of its own ancestors would be walked forever
|
|
144
|
+
async function isCyclicLink(link) {
|
|
145
|
+
const target = await fsp.realpath(link.getName());
|
|
146
|
+
for (const ancestor of ancestorsOf(link)) {
|
|
147
|
+
if (await fsp.realpath(ancestor) === target) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
// Broken links are skipped, as are all links when ignoreSymlinks is set
|
|
154
|
+
async function isVisitableLink(link, walk) {
|
|
155
|
+
if (walk.ignoreSymlinks) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return !(await link.isDirectory() && await isCyclicLink(link));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const NOTHING = Object.freeze({});
|
|
166
|
+
// Links have no known kind, so need a stat
|
|
167
|
+
async function visitUntyped(file, walk, depth) {
|
|
168
|
+
return visitTyped(file, typeOf(await file.getStats()), walk, depth);
|
|
169
|
+
}
|
|
170
|
+
async function visitLink(link, walk, depth) {
|
|
171
|
+
return await isVisitableLink(link, walk) ? visitUntyped(link, walk, depth) : NOTHING;
|
|
172
|
+
}
|
|
173
|
+
function visitTyped(file, type, walk, depth) {
|
|
174
|
+
if (type === EntryType.DIRECTORY && walk.shouldSkipDirectory(file)) {
|
|
175
|
+
return NOTHING;
|
|
176
|
+
}
|
|
177
|
+
const children = type === EntryType.DIRECTORY && walk.canDescend(depth) ? walk.read(file, depth) : undefined;
|
|
178
|
+
if (!walk.isCandidate(file, type)) {
|
|
179
|
+
return children ? { children } : NOTHING;
|
|
180
|
+
}
|
|
181
|
+
if (walk.isSyncMatcher) {
|
|
182
|
+
return walk.matcher.testSync(file) ? { match: file, children } : children ? { children } : NOTHING;
|
|
183
|
+
}
|
|
184
|
+
return walk.matcher.test(file).then(isMatch => ({ match: isMatch ? file : undefined, children }));
|
|
185
|
+
}
|
|
186
|
+
// Children always have a dirent, so only links lack a known kind
|
|
187
|
+
function visitChild(child, walk, depth) {
|
|
188
|
+
const kind = getKnownKind(child);
|
|
189
|
+
const visited = kind ? visitTyped(child, typeOf(kind), walk, depth) : visitLink(child, walk, depth);
|
|
190
|
+
return visited instanceof Promise ? prefetch(visited) : visited;
|
|
191
|
+
}
|
|
192
|
+
// Matches are yielded in batches, in walk order: an entry, then everything
|
|
193
|
+
// below it, then its next sibling, with siblings in name order. A batch is
|
|
194
|
+
// yielded before walking down, so matches arrive as they are found.
|
|
195
|
+
async function* walkChildren(children, walk, depth) {
|
|
196
|
+
const visits = [];
|
|
197
|
+
let started = 0;
|
|
198
|
+
let matches = [];
|
|
199
|
+
for (let next = 0; next < children.length; next++) {
|
|
200
|
+
for (const end = Math.min(next + WINDOW, children.length); started < end; started++) {
|
|
201
|
+
visits[started] = visitChild(children[started], walk, depth);
|
|
202
|
+
}
|
|
203
|
+
const visited = visits[next];
|
|
204
|
+
visits[next] = undefined;
|
|
205
|
+
const visit = visited instanceof Promise ? await walk.abortable(visited) : visited;
|
|
206
|
+
if (visit.match) {
|
|
207
|
+
matches.push(visit.match);
|
|
208
|
+
}
|
|
209
|
+
if (visit.children) {
|
|
210
|
+
if (matches.length > 0) {
|
|
211
|
+
yield matches;
|
|
212
|
+
walk.throwIfAborted();
|
|
213
|
+
matches = [];
|
|
214
|
+
}
|
|
215
|
+
yield* walkChildren(await walk.abortable(visit.children), walk, depth + 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (matches.length > 0) {
|
|
219
|
+
yield matches;
|
|
220
|
+
walk.throwIfAborted();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export async function* walkAsync(dir, matcher, opts) {
|
|
224
|
+
opts.signal?.throwIfAborted();
|
|
225
|
+
const root = new File(dir);
|
|
226
|
+
let stats;
|
|
227
|
+
try {
|
|
228
|
+
stats = await root.getStats();
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw createPathError(dir, error);
|
|
232
|
+
}
|
|
233
|
+
assertDirectory(dir, stats);
|
|
234
|
+
// The root is never a match, only the place to search, so the walk starts at its children
|
|
235
|
+
const walk = new AsyncWalk(matcher, opts);
|
|
236
|
+
yield* walkChildren(await walk.abortable(walk.read(root, 0)), walk, 1);
|
|
237
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// A binary min-heap: pop returns the item that comes first by precedes
|
|
2
|
+
export class Heap {
|
|
3
|
+
#items = [];
|
|
4
|
+
#precedes;
|
|
5
|
+
constructor(precedes) {
|
|
6
|
+
this.#precedes = precedes;
|
|
7
|
+
}
|
|
8
|
+
get size() {
|
|
9
|
+
return this.#items.length;
|
|
10
|
+
}
|
|
11
|
+
push(item) {
|
|
12
|
+
const items = this.#items;
|
|
13
|
+
let i = items.push(item) - 1;
|
|
14
|
+
while (i > 0) {
|
|
15
|
+
const parent = (i - 1) >> 1;
|
|
16
|
+
if (!this.#precedes(items[i], items[parent])) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
[items[i], items[parent]] = [items[parent], items[i]];
|
|
20
|
+
i = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
pop() {
|
|
24
|
+
const items = this.#items;
|
|
25
|
+
const first = items[0];
|
|
26
|
+
const last = items.pop();
|
|
27
|
+
if (items.length > 0 && last !== undefined) {
|
|
28
|
+
items[0] = last;
|
|
29
|
+
let i = 0;
|
|
30
|
+
for (;;) {
|
|
31
|
+
const left = 2 * i + 1;
|
|
32
|
+
const right = left + 1;
|
|
33
|
+
let next = i;
|
|
34
|
+
if (left < items.length && this.#precedes(items[left], items[next])) {
|
|
35
|
+
next = left;
|
|
36
|
+
}
|
|
37
|
+
if (right < items.length && this.#precedes(items[right], items[next])) {
|
|
38
|
+
next = right;
|
|
39
|
+
}
|
|
40
|
+
if (next === i) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
[items[i], items[next]] = [items[next], items[i]];
|
|
44
|
+
i = next;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return first;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { File, getChildrenSync, getKnownKind } from '../file.js';
|
|
3
|
+
import { ancestorsOf, assertDirectory, byName, createPathError, EntryType, typeOf, Walk } from './walk.js';
|
|
4
|
+
// A linked directory that resolves to one of its own ancestors would be walked forever
|
|
5
|
+
function isCyclicLink(link) {
|
|
6
|
+
const target = fs.realpathSync(link.getName());
|
|
7
|
+
return ancestorsOf(link).some(ancestor => fs.realpathSync(ancestor) === target);
|
|
8
|
+
}
|
|
9
|
+
// Broken links are skipped, as are all links when ignoreSymlinks is set
|
|
10
|
+
function isVisitableLink(link, walk) {
|
|
11
|
+
if (walk.ignoreSymlinks) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return !(link.isDirectorySync() && isCyclicLink(link));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Links have no known kind, so need a stat
|
|
22
|
+
function visitUntyped(file, walk, depth, matches) {
|
|
23
|
+
return visitTyped(file, typeOf(file.getStatsSync()), walk, depth, matches);
|
|
24
|
+
}
|
|
25
|
+
function visitLink(link, walk, depth, matches) {
|
|
26
|
+
return isVisitableLink(link, walk) ? visitUntyped(link, walk, depth, matches) : matches;
|
|
27
|
+
}
|
|
28
|
+
// Entries are visited depth first in name order, adding to a single list of matches
|
|
29
|
+
function visitTyped(file, type, walk, depth, matches) {
|
|
30
|
+
if (type === EntryType.DIRECTORY && walk.shouldSkipDirectory(file)) {
|
|
31
|
+
return matches;
|
|
32
|
+
}
|
|
33
|
+
if (walk.isCandidate(file, type) && walk.matcher.testSync(file)) {
|
|
34
|
+
matches.push(file);
|
|
35
|
+
}
|
|
36
|
+
if (type === EntryType.DIRECTORY && walk.canDescend(depth)) {
|
|
37
|
+
visitChildren(file, walk, depth + 1, matches);
|
|
38
|
+
}
|
|
39
|
+
return matches;
|
|
40
|
+
}
|
|
41
|
+
// Children always have a dirent, so only links lack a known kind
|
|
42
|
+
function visitChildren(dir, walk, depth, matches) {
|
|
43
|
+
for (const child of getChildrenSync(dir).sort(byName)) {
|
|
44
|
+
const kind = getKnownKind(child);
|
|
45
|
+
if (kind) {
|
|
46
|
+
visitTyped(child, typeOf(kind), walk, depth, matches);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
visitLink(child, walk, depth, matches);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
export function walkSync(dir, matcher, opts) {
|
|
55
|
+
const root = new File(dir);
|
|
56
|
+
let stats;
|
|
57
|
+
try {
|
|
58
|
+
stats = root.getStatsSync();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw createPathError(dir, error);
|
|
62
|
+
}
|
|
63
|
+
assertDirectory(dir, stats);
|
|
64
|
+
// The root is never a match, only the place to search, so the walk starts at its children
|
|
65
|
+
return visitChildren(root, new Walk(matcher, opts), 1, []);
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { File, FileKind } from '../file.js';
|
|
2
|
+
import { FileType } from '../filetype.js';
|
|
3
|
+
import { type FilePredicate } from '../matchers.js';
|
|
4
|
+
export type WalkOptions = {
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
ignoreHiddenFiles: boolean;
|
|
7
|
+
ignoreHiddenDirectories: boolean;
|
|
8
|
+
ignoreSymlinks: boolean;
|
|
9
|
+
fileTypes: FileType[];
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
};
|
|
12
|
+
export declare const EntryType: {
|
|
13
|
+
readonly OTHER: "other";
|
|
14
|
+
readonly REGULAR: "regular";
|
|
15
|
+
readonly DIRECTORY: "directory";
|
|
16
|
+
};
|
|
17
|
+
export type EntryType = typeof EntryType[keyof typeof EntryType];
|
|
18
|
+
export declare function createPathError(name: string, cause: unknown): Error;
|
|
19
|
+
export declare function assertDirectory(name: string, stats: FileKind): void;
|
|
20
|
+
export declare class Walk {
|
|
21
|
+
#private;
|
|
22
|
+
readonly matcher: FilePredicate;
|
|
23
|
+
readonly isSyncMatcher: boolean;
|
|
24
|
+
constructor(matcher: FilePredicate, opts: WalkOptions);
|
|
25
|
+
get ignoreSymlinks(): boolean;
|
|
26
|
+
isCandidate(file: File, type: EntryType): boolean;
|
|
27
|
+
shouldSkipDirectory(dir: File): boolean;
|
|
28
|
+
canDescend(depth: number): boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function typeOf(kind: FileKind): EntryType;
|
|
31
|
+
export declare function byName(a: File, b: File): number;
|
|
32
|
+
export declare function ancestorsOf(file: File): string[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { FileType } from '../filetype.js';
|
|
3
|
+
import { isSyncPredicate } from '../matchers.js';
|
|
4
|
+
// Every entry has one. FileType is the public subset; anything else, such as a
|
|
5
|
+
// socket or FIFO, is OTHER and never matched.
|
|
6
|
+
export const EntryType = { ...FileType, OTHER: 'other' };
|
|
7
|
+
export function createPathError(name, cause) {
|
|
8
|
+
return new Error(`'${name}' is not a valid path`, { cause });
|
|
9
|
+
}
|
|
10
|
+
// Search paths are where to search, so must be directories
|
|
11
|
+
export function assertDirectory(name, stats) {
|
|
12
|
+
if (!stats.isDirectory()) {
|
|
13
|
+
throw new Error(`'${name}' is not a directory`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// State shared by a walk. The async and sync walks (async.ts and sync.ts) share
|
|
17
|
+
// everything except the calls that touch the file system, and both return
|
|
18
|
+
// matches in name order.
|
|
19
|
+
export class Walk {
|
|
20
|
+
matcher;
|
|
21
|
+
// Checked once, as combined predicates work it out from their parts
|
|
22
|
+
isSyncMatcher;
|
|
23
|
+
#opts;
|
|
24
|
+
constructor(matcher, opts) {
|
|
25
|
+
this.matcher = matcher;
|
|
26
|
+
this.isSyncMatcher = isSyncPredicate(matcher);
|
|
27
|
+
this.#opts = opts;
|
|
28
|
+
}
|
|
29
|
+
get ignoreSymlinks() {
|
|
30
|
+
return this.#opts.ignoreSymlinks;
|
|
31
|
+
}
|
|
32
|
+
isCandidate(file, type) {
|
|
33
|
+
switch (type) {
|
|
34
|
+
case EntryType.DIRECTORY:
|
|
35
|
+
return this.#typeEnabled(type);
|
|
36
|
+
case EntryType.REGULAR:
|
|
37
|
+
return this.#typeEnabled(type) && !(this.#opts.ignoreHiddenFiles && file.isHiddenSync());
|
|
38
|
+
case EntryType.OTHER:
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Hidden directories are neither returned nor walked
|
|
43
|
+
shouldSkipDirectory(dir) {
|
|
44
|
+
return this.#opts.ignoreHiddenDirectories && dir.isHiddenSync();
|
|
45
|
+
}
|
|
46
|
+
// depth counts levels below the root, which is 0. A directory beyond
|
|
47
|
+
// maxDepth is still returned, like any other entry at its level, but not walked.
|
|
48
|
+
canDescend(depth) {
|
|
49
|
+
return this.#opts.maxDepth === undefined || depth <= this.#opts.maxDepth;
|
|
50
|
+
}
|
|
51
|
+
#typeEnabled(type) {
|
|
52
|
+
return this.#opts.fileTypes.includes(type);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function typeOf(kind) {
|
|
56
|
+
return kind.isDirectory() ? EntryType.DIRECTORY : kind.isFile() ? EntryType.REGULAR : EntryType.OTHER;
|
|
57
|
+
}
|
|
58
|
+
export function byName(a, b) {
|
|
59
|
+
return a.getName() < b.getName() ? -1 : a.getName() > b.getName() ? 1 : 0;
|
|
60
|
+
}
|
|
61
|
+
export function ancestorsOf(file) {
|
|
62
|
+
const ancestors = [];
|
|
63
|
+
let ancestor = dirname(resolve(file.getName()));
|
|
64
|
+
while (dirname(ancestor) !== ancestor) {
|
|
65
|
+
ancestors.push(ancestor);
|
|
66
|
+
ancestor = dirname(ancestor);
|
|
67
|
+
}
|
|
68
|
+
ancestors.push(ancestor);
|
|
69
|
+
return ancestors;
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "filehound",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.3",
|
|
4
4
|
"description": "Find files the easy way",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=24"
|
|
19
|
+
},
|
|
6
20
|
"scripts": {
|
|
7
|
-
"
|
|
21
|
+
"docs": "typedoc",
|
|
8
22
|
"build": "tsc",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
9
24
|
"watch": "tsc --watch",
|
|
10
|
-
"clean": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"test": "mocha -r ts-node/register test/*.ts",
|
|
25
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
26
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
27
|
+
"test": "mocha",
|
|
14
28
|
"posttest": "npm run lint",
|
|
15
|
-
"lint": "
|
|
16
|
-
"coverage": "
|
|
29
|
+
"lint": "eslint src test bench",
|
|
30
|
+
"coverage": "c8 mocha",
|
|
31
|
+
"bench": "npm run build && node bench/bench.mjs"
|
|
17
32
|
},
|
|
18
33
|
"repository": {
|
|
19
34
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/nspragg/filehound.git"
|
|
35
|
+
"url": "git+https://github.com/nspragg/filehound.git"
|
|
21
36
|
},
|
|
22
37
|
"keywords": [
|
|
23
38
|
"file",
|
|
24
39
|
"recursive",
|
|
25
40
|
"search",
|
|
26
41
|
"filehound",
|
|
27
|
-
"file",
|
|
28
42
|
"hound",
|
|
29
43
|
"lister",
|
|
30
44
|
"find",
|
|
@@ -36,48 +50,30 @@
|
|
|
36
50
|
"fs",
|
|
37
51
|
"file system"
|
|
38
52
|
],
|
|
53
|
+
"author": "nspragg <nspragg@gmail.com>",
|
|
39
54
|
"license": "MIT",
|
|
40
55
|
"bugs": {
|
|
41
56
|
"url": "https://github.com/nspragg/filehound/issues"
|
|
42
57
|
},
|
|
43
58
|
"homepage": "https://github.com/nspragg/filehound",
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"@types/mocha": "^2.2.43",
|
|
46
|
-
"@types/node": "^8.0.33",
|
|
47
|
-
"@types/typescript": "^2.0.0",
|
|
48
|
-
"eslint-config-iplayer-base": "^1.0.0",
|
|
49
|
-
"chai": "^4.1.2",
|
|
50
|
-
"coveralls": "^2.11.9",
|
|
51
|
-
"eslint": "^3.11.1",
|
|
52
|
-
"istanbul": "1.1.0-alpha.1",
|
|
53
|
-
"jsdoc": "^3.4.3",
|
|
54
|
-
"mocha": "^5.0.5",
|
|
55
|
-
"sinon": "^2.1.0",
|
|
56
|
-
"ts-node": "^3.3.0",
|
|
57
|
-
"tslint": "^5.8.0",
|
|
58
|
-
"tslint-config-airbnb": "^5.3.1",
|
|
59
|
-
"tslint-eslint-rules": "^4.1.1",
|
|
60
|
-
"typescript": "^2.6.1"
|
|
61
|
-
},
|
|
62
|
-
"nyc": {
|
|
63
|
-
"include": [
|
|
64
|
-
"src/**/*.ts"
|
|
65
|
-
],
|
|
66
|
-
"extension": [
|
|
67
|
-
".ts"
|
|
68
|
-
],
|
|
69
|
-
"require": [
|
|
70
|
-
"ts-node/register"
|
|
71
|
-
],
|
|
72
|
-
"sourceMap": true,
|
|
73
|
-
"instrument": true
|
|
74
|
-
},
|
|
75
59
|
"dependencies": {
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
60
|
+
"unit-compare": "^2.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@eslint/js": "^10.0.1",
|
|
64
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
65
|
+
"@types/chai": "^5.2.3",
|
|
66
|
+
"@types/mocha": "^10.0.10",
|
|
67
|
+
"@types/node": "^24.19.2",
|
|
68
|
+
"@types/sinon": "^22.0.2",
|
|
69
|
+
"c8": "^12.0.0",
|
|
70
|
+
"chai": "^6.3.0",
|
|
71
|
+
"eslint": "^10.13.0",
|
|
72
|
+
"mocha": "^12.0.3",
|
|
73
|
+
"sinon": "^22.1.0",
|
|
74
|
+
"tsx": "^4.23.15",
|
|
75
|
+
"typedoc": "^0.28.20",
|
|
76
|
+
"typescript": "~6.0",
|
|
77
|
+
"typescript-eslint": "^8.72.1"
|
|
82
78
|
}
|
|
83
79
|
}
|
package/.github/stale.yml
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Configuration for probot-stale - https://github.com/probot/stale
|
|
2
|
-
|
|
3
|
-
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
4
|
-
daysUntilStale: 5
|
|
5
|
-
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
|
6
|
-
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
7
|
-
daysUntilClose: 7
|
|
8
|
-
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
9
|
-
exemptLabels:
|
|
10
|
-
- pinned
|
|
11
|
-
- security
|
|
12
|
-
- "[Status] Maybe Later"
|
|
13
|
-
# Set to true to ignore issues in a project (defaults to false)
|
|
14
|
-
exemptProjects: false
|
|
15
|
-
# Set to true to ignore issues in a milestone (defaults to false)
|
|
16
|
-
exemptMilestones: false
|
|
17
|
-
# Label to use when marking as stale
|
|
18
|
-
staleLabel: wontfix
|
|
19
|
-
# Comment to post when marking as stale. Set to `false` to disable
|
|
20
|
-
markComment: >
|
|
21
|
-
This issue has been automatically marked as stale because it has not had
|
|
22
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
23
|
-
for your contributions.
|
|
24
|
-
# Comment to post when removing the stale label. Set to `false` to disable
|
|
25
|
-
unmarkComment: false
|
|
26
|
-
# Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable
|
|
27
|
-
closeComment: false
|
|
28
|
-
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
29
|
-
limitPerRun: 30
|
|
30
|
-
# Limit to only `issues` or `pulls`
|
|
31
|
-
# only: issues
|
|
32
|
-
#
|
|
33
|
-
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
|
34
|
-
# pulls:
|
|
35
|
-
# daysUntilStale: 30
|
|
36
|
-
# markComment: >
|
|
37
|
-
# This pull request has been automatically marked as stale because it has not had
|
|
38
|
-
# recent activity. It will be closed if no further activity occurs. Thank you
|
|
39
|
-
# for your contributions.
|
|
40
|
-
# issues:
|
|
41
|
-
# exemptLabels:
|
|
42
|
-
# - confirmed
|
package/.travis.yml
DELETED
package/.vscode/launch.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Use IntelliSense to learn about possible Node.js debug attributes.
|
|
3
|
-
// Hover to view descriptions of existing attributes.
|
|
4
|
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
-
"version": "0.2.0",
|
|
6
|
-
"configurations": [
|
|
7
|
-
{
|
|
8
|
-
"type": "node",
|
|
9
|
-
"request": "launch",
|
|
10
|
-
"name": "Launch Program",
|
|
11
|
-
"program": "${workspaceRoot}/index.js"
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"type": "node",
|
|
15
|
-
"request": "attach",
|
|
16
|
-
"name": "Attach to Process",
|
|
17
|
-
"address": "localhost",
|
|
18
|
-
"port": 5858
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
package/.vscode/settings.json
DELETED