dsh-remote 0.8.72 → 0.8.73
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/LICENSE +21 -21
- package/README.en.md +389 -389
- package/README.md +462 -462
- package/cordis.patch.yml +9 -9
- package/lib/binding.js +172 -172
- package/lib/client.js +4022 -4022
- package/lib/credential.js +263 -263
- package/lib/errors.js +141 -141
- package/lib/exec-job.js +235 -235
- package/lib/file-reference.js +457 -457
- package/lib/forwards.js +258 -258
- package/lib/hostkey.js +108 -108
- package/lib/http-transport.js +196 -196
- package/lib/ignore.js +202 -202
- package/lib/index.js +4550 -4540
- package/lib/instructions-dynamic.js +415 -415
- package/lib/openssh.js +879 -879
- package/lib/paths.js +252 -252
- package/lib/pool.js +963 -893
- package/lib/registry.js +296 -293
- package/lib/remote-fs.js +173 -173
- package/lib/remote-sandbox.js +183 -183
- package/lib/remote-shell-child.cjs +344 -305
- package/lib/remote-shell.js +542 -518
- package/lib/routes-fs.js +245 -245
- package/lib/routes-term.js +169 -169
- package/lib/search.js +235 -235
- package/lib/sftp-surface.js +119 -119
- package/lib/sshconfig.js +779 -705
- package/lib/sync.js +275 -275
- package/lib/tasks.js +95 -95
- package/lib/telemetry.js +213 -213
- package/lib/terminal.js +205 -205
- package/lib/update.js +472 -472
- package/lib/web-attach.js +850 -850
- package/package.json +100 -99
package/lib/ignore.js
CHANGED
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
// dsh-remote — gitignore-style ignore matcher for mirror sync / remote search.
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
//
|
|
4
|
-
// Deliberately a SUBSET of gitignore semantics (documented, no surprises):
|
|
5
|
-
// - blank lines and `#` comments are skipped
|
|
6
|
-
// - `!` negation is NOT supported (later rules never un-ignore)
|
|
7
|
-
// - a trailing `/` matches directories only (and everything under them)
|
|
8
|
-
// - a pattern containing `/` (or starting with `/`) is anchored to the
|
|
9
|
-
// workspace root; otherwise it matches the basename at ANY depth
|
|
10
|
-
// - `*` matches any run of chars except `/`; `**` matches any run incl. `/`;
|
|
11
|
-
// `?` matches one char except `/`
|
|
12
|
-
// - character classes (`[abc]`) are not supported (treated literally)
|
|
13
|
-
|
|
14
|
-
/** Convert one glob segment to a regex source (no `/` crossing). A leading
|
|
15
|
-
* double-star-slash (`**` + `/`) matches zero or more directories (gitignore). */
|
|
16
|
-
function globToRe(glob) {
|
|
17
|
-
let out = ''
|
|
18
|
-
let i = 0
|
|
19
|
-
const s = String(glob)
|
|
20
|
-
if (s.startsWith('**/')) { out = '(?:.*/)?'; i = 3 }
|
|
21
|
-
while (i < s.length) {
|
|
22
|
-
const c = s[i]
|
|
23
|
-
if (c === '*') {
|
|
24
|
-
if (s[i + 1] === '*') { out += '.*'; i += 2; continue }
|
|
25
|
-
out += '[^/]*'; i++; continue
|
|
26
|
-
}
|
|
27
|
-
if (c === '?') { out += '[^/]'; i++; continue }
|
|
28
|
-
if ('\\^$+{}[]()|.'.includes(c)) out += '\\' + c
|
|
29
|
-
else out += c
|
|
30
|
-
i++
|
|
31
|
-
}
|
|
32
|
-
return out
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Compile a list of ignore patterns into a matcher.
|
|
37
|
-
* @param {string[]|string} patterns lines or an array of lines
|
|
38
|
-
* @returns {(relPath: string, isDir?: boolean) => boolean} true = ignored.
|
|
39
|
-
* `relPath` is POSIX-style ('/' separated, no leading '/').
|
|
40
|
-
*/
|
|
41
|
-
export function compileIgnore(patterns) {
|
|
42
|
-
const rules = []
|
|
43
|
-
const lines = Array.isArray(patterns) ? patterns : String(patterns || '').split('\n')
|
|
44
|
-
for (let raw of lines) {
|
|
45
|
-
let line = String(raw || '').trim()
|
|
46
|
-
if (!line || line.startsWith('#')) continue
|
|
47
|
-
let dirOnly = false
|
|
48
|
-
if (line.endsWith('/')) { dirOnly = true; line = line.slice(0, -1) }
|
|
49
|
-
let anchored = line.startsWith('/')
|
|
50
|
-
if (anchored) line = line.slice(1)
|
|
51
|
-
if (!line) continue
|
|
52
|
-
const hasSlash = line.includes('/')
|
|
53
|
-
const isAnchored = anchored || hasSlash
|
|
54
|
-
const segRe = globToRe(line)
|
|
55
|
-
let re
|
|
56
|
-
if (isAnchored) {
|
|
57
|
-
// Root-anchored: match the full path from the root.
|
|
58
|
-
re = dirOnly
|
|
59
|
-
? new RegExp('^' + segRe + '(/.*)?$')
|
|
60
|
-
: new RegExp('^' + segRe + '$')
|
|
61
|
-
} else {
|
|
62
|
-
// Basename at any depth; a dir pattern also covers everything under it.
|
|
63
|
-
re = dirOnly
|
|
64
|
-
? new RegExp('(^|/)' + segRe + '(/|$)')
|
|
65
|
-
: new RegExp('(^|/)' + segRe + '$')
|
|
66
|
-
}
|
|
67
|
-
rules.push({ re, dirOnly })
|
|
68
|
-
}
|
|
69
|
-
return function isIgnored(relPath, isDir) {
|
|
70
|
-
const rel = String(relPath || '').replace(/^\/+/, '').replace(/\\/g, '/')
|
|
71
|
-
if (!rel) return false
|
|
72
|
-
for (const r of rules) {
|
|
73
|
-
if (r.re.test(rel)) return true
|
|
74
|
-
}
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Built-in defaults: never pull/push these into/from a mirror. */
|
|
80
|
-
export const DEFAULT_IGNORE = [
|
|
81
|
-
'.git/',
|
|
82
|
-
'.svn/',
|
|
83
|
-
'.hg/',
|
|
84
|
-
'node_modules/',
|
|
85
|
-
'target/',
|
|
86
|
-
'dist/',
|
|
87
|
-
'build/',
|
|
88
|
-
'.venv/',
|
|
89
|
-
'venv/',
|
|
90
|
-
'__pycache__/',
|
|
91
|
-
'.idea/',
|
|
92
|
-
'.vscode/',
|
|
93
|
-
'*.o',
|
|
94
|
-
'*.a',
|
|
95
|
-
'*.so',
|
|
96
|
-
'*.dylib',
|
|
97
|
-
'*.exe',
|
|
98
|
-
'*.dll',
|
|
99
|
-
'*.class',
|
|
100
|
-
'*.jar',
|
|
101
|
-
'*.log',
|
|
102
|
-
'*.tmp',
|
|
103
|
-
'.DS_Store',
|
|
104
|
-
'Thumbs.db',
|
|
105
|
-
'.dsh-remote-meta.json',
|
|
106
|
-
'.dsh-remote-sync-state.json',
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Extra directories skipped by `rw_search` (but NOT by mirror sync — a user may
|
|
111
|
-
* legitimately want their cache tree mirrored, and silently excluding it from
|
|
112
|
-
* sync would lose data).
|
|
113
|
-
*
|
|
114
|
-
* These are machine-local package/tool caches: enormous numbers of tiny files
|
|
115
|
-
* that essentially never contain what a human is searching for, and that are
|
|
116
|
-
* the main way a "search my home directory" turns into a multi-hour walk
|
|
117
|
-
* (issue #44 reports `~/.npm/_cacache` specifically). Searching them is
|
|
118
|
-
* available via an explicit `path`, which bypasses these excludes.
|
|
119
|
-
*/
|
|
120
|
-
export const DEFAULT_SEARCH_IGNORE = [
|
|
121
|
-
...DEFAULT_IGNORE,
|
|
122
|
-
'.npm/',
|
|
123
|
-
'.cache/',
|
|
124
|
-
'.local/',
|
|
125
|
-
'.cargo/',
|
|
126
|
-
'.rustup/',
|
|
127
|
-
'.gradle/',
|
|
128
|
-
'.m2/',
|
|
129
|
-
'.pnpm-store/',
|
|
130
|
-
'.yarn/',
|
|
131
|
-
'.conda/',
|
|
132
|
-
'.docker/',
|
|
133
|
-
'.nvm/',
|
|
134
|
-
'.pyenv/',
|
|
135
|
-
'.Trash/',
|
|
136
|
-
'.Trash-1000/',
|
|
137
|
-
'snap/',
|
|
138
|
-
'AppData/',
|
|
139
|
-
'Library/Caches/',
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Whether a normalized remote path is a FILESYSTEM ROOT: POSIX `/`, a Windows
|
|
144
|
-
* drive root (`D:\`), or a UNC share root (`\\server\share`).
|
|
145
|
-
*
|
|
146
|
-
* Root workspaces are legal (issue #60) but dangerous to walk naively: `/proc`
|
|
147
|
-
* and `/sys` are synthetic (new pids appear between readdirs, `/proc/kcore`
|
|
148
|
-
* advertises 128 TB), `/dev` holds never-ending character devices, and
|
|
149
|
-
* `/var/lib/docker` is tens of GB of image layers. ROOT_IGNORE exists so a
|
|
150
|
-
* root workspace skips exactly those trees.
|
|
151
|
-
*/
|
|
152
|
-
export function isFilesystemRoot(p) {
|
|
153
|
-
const s = String(p || '')
|
|
154
|
-
return s === '/' || /^[a-zA-Z]:\\?$/.test(s) || /^\\\\[^\\]+\\[^\\]+$/.test(s)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Patterns merged in ONLY when the workspace is the filesystem root
|
|
159
|
-
* (issue #60). Every entry is root-anchored AND directory-only, so even when
|
|
160
|
-
* merged they can only match at the workspace top level: `/` skips `/proc`,
|
|
161
|
-
* while a hypothetical `/home/u/proc` under a non-root workspace is ordinary
|
|
162
|
-
* content (and non-root workspaces never receive these patterns at all, so
|
|
163
|
-
* their behaviour is byte-identical to before).
|
|
164
|
-
*/
|
|
165
|
-
export const ROOT_IGNORE = [
|
|
166
|
-
'/proc/',
|
|
167
|
-
'/sys/',
|
|
168
|
-
'/dev/',
|
|
169
|
-
'/run/',
|
|
170
|
-
'/boot/',
|
|
171
|
-
'/var/lib/docker/',
|
|
172
|
-
]
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Effective ignore patterns for a workspace rooted at `root`:
|
|
176
|
-
* the defaults, plus ROOT_IGNORE only for a root workspace, plus the user's
|
|
177
|
-
* own `.dsh-remote-ignore` lines (always last, they stack on top).
|
|
178
|
-
*
|
|
179
|
-
* @param {string} root - workspace root as passed to sync/push/search.
|
|
180
|
-
* @param {{search?: boolean, fromFile?: string[]}} [opts]
|
|
181
|
-
*/
|
|
182
|
-
export function ignorePatternsFor(root, { search = false, fromFile = [] } = {}) {
|
|
183
|
-
const base = search ? DEFAULT_SEARCH_IGNORE : DEFAULT_IGNORE
|
|
184
|
-
const extra = isFilesystemRoot(root) ? ROOT_IGNORE : []
|
|
185
|
-
return base.concat(extra, fromFile)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Load the user ignore file (gitignore syntax) and merge with defaults.
|
|
190
|
-
* Returns `{ patterns, fromFile }`.
|
|
191
|
-
*/
|
|
192
|
-
export function loadIgnore(ignoreFile) {
|
|
193
|
-
let fromFile = []
|
|
194
|
-
if (ignoreFile) {
|
|
195
|
-
try {
|
|
196
|
-
const text = readFileSync(ignoreFile, 'utf8')
|
|
197
|
-
fromFile = text.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'))
|
|
198
|
-
} catch { /* missing file → defaults only */ }
|
|
199
|
-
}
|
|
200
|
-
const patterns = DEFAULT_IGNORE.concat(fromFile)
|
|
201
|
-
return { patterns, fromFile, matcher: compileIgnore(patterns) }
|
|
202
|
-
}
|
|
1
|
+
// dsh-remote — gitignore-style ignore matcher for mirror sync / remote search.
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
//
|
|
4
|
+
// Deliberately a SUBSET of gitignore semantics (documented, no surprises):
|
|
5
|
+
// - blank lines and `#` comments are skipped
|
|
6
|
+
// - `!` negation is NOT supported (later rules never un-ignore)
|
|
7
|
+
// - a trailing `/` matches directories only (and everything under them)
|
|
8
|
+
// - a pattern containing `/` (or starting with `/`) is anchored to the
|
|
9
|
+
// workspace root; otherwise it matches the basename at ANY depth
|
|
10
|
+
// - `*` matches any run of chars except `/`; `**` matches any run incl. `/`;
|
|
11
|
+
// `?` matches one char except `/`
|
|
12
|
+
// - character classes (`[abc]`) are not supported (treated literally)
|
|
13
|
+
|
|
14
|
+
/** Convert one glob segment to a regex source (no `/` crossing). A leading
|
|
15
|
+
* double-star-slash (`**` + `/`) matches zero or more directories (gitignore). */
|
|
16
|
+
function globToRe(glob) {
|
|
17
|
+
let out = ''
|
|
18
|
+
let i = 0
|
|
19
|
+
const s = String(glob)
|
|
20
|
+
if (s.startsWith('**/')) { out = '(?:.*/)?'; i = 3 }
|
|
21
|
+
while (i < s.length) {
|
|
22
|
+
const c = s[i]
|
|
23
|
+
if (c === '*') {
|
|
24
|
+
if (s[i + 1] === '*') { out += '.*'; i += 2; continue }
|
|
25
|
+
out += '[^/]*'; i++; continue
|
|
26
|
+
}
|
|
27
|
+
if (c === '?') { out += '[^/]'; i++; continue }
|
|
28
|
+
if ('\\^$+{}[]()|.'.includes(c)) out += '\\' + c
|
|
29
|
+
else out += c
|
|
30
|
+
i++
|
|
31
|
+
}
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Compile a list of ignore patterns into a matcher.
|
|
37
|
+
* @param {string[]|string} patterns lines or an array of lines
|
|
38
|
+
* @returns {(relPath: string, isDir?: boolean) => boolean} true = ignored.
|
|
39
|
+
* `relPath` is POSIX-style ('/' separated, no leading '/').
|
|
40
|
+
*/
|
|
41
|
+
export function compileIgnore(patterns) {
|
|
42
|
+
const rules = []
|
|
43
|
+
const lines = Array.isArray(patterns) ? patterns : String(patterns || '').split('\n')
|
|
44
|
+
for (let raw of lines) {
|
|
45
|
+
let line = String(raw || '').trim()
|
|
46
|
+
if (!line || line.startsWith('#')) continue
|
|
47
|
+
let dirOnly = false
|
|
48
|
+
if (line.endsWith('/')) { dirOnly = true; line = line.slice(0, -1) }
|
|
49
|
+
let anchored = line.startsWith('/')
|
|
50
|
+
if (anchored) line = line.slice(1)
|
|
51
|
+
if (!line) continue
|
|
52
|
+
const hasSlash = line.includes('/')
|
|
53
|
+
const isAnchored = anchored || hasSlash
|
|
54
|
+
const segRe = globToRe(line)
|
|
55
|
+
let re
|
|
56
|
+
if (isAnchored) {
|
|
57
|
+
// Root-anchored: match the full path from the root.
|
|
58
|
+
re = dirOnly
|
|
59
|
+
? new RegExp('^' + segRe + '(/.*)?$')
|
|
60
|
+
: new RegExp('^' + segRe + '$')
|
|
61
|
+
} else {
|
|
62
|
+
// Basename at any depth; a dir pattern also covers everything under it.
|
|
63
|
+
re = dirOnly
|
|
64
|
+
? new RegExp('(^|/)' + segRe + '(/|$)')
|
|
65
|
+
: new RegExp('(^|/)' + segRe + '$')
|
|
66
|
+
}
|
|
67
|
+
rules.push({ re, dirOnly })
|
|
68
|
+
}
|
|
69
|
+
return function isIgnored(relPath, isDir) {
|
|
70
|
+
const rel = String(relPath || '').replace(/^\/+/, '').replace(/\\/g, '/')
|
|
71
|
+
if (!rel) return false
|
|
72
|
+
for (const r of rules) {
|
|
73
|
+
if (r.re.test(rel)) return true
|
|
74
|
+
}
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Built-in defaults: never pull/push these into/from a mirror. */
|
|
80
|
+
export const DEFAULT_IGNORE = [
|
|
81
|
+
'.git/',
|
|
82
|
+
'.svn/',
|
|
83
|
+
'.hg/',
|
|
84
|
+
'node_modules/',
|
|
85
|
+
'target/',
|
|
86
|
+
'dist/',
|
|
87
|
+
'build/',
|
|
88
|
+
'.venv/',
|
|
89
|
+
'venv/',
|
|
90
|
+
'__pycache__/',
|
|
91
|
+
'.idea/',
|
|
92
|
+
'.vscode/',
|
|
93
|
+
'*.o',
|
|
94
|
+
'*.a',
|
|
95
|
+
'*.so',
|
|
96
|
+
'*.dylib',
|
|
97
|
+
'*.exe',
|
|
98
|
+
'*.dll',
|
|
99
|
+
'*.class',
|
|
100
|
+
'*.jar',
|
|
101
|
+
'*.log',
|
|
102
|
+
'*.tmp',
|
|
103
|
+
'.DS_Store',
|
|
104
|
+
'Thumbs.db',
|
|
105
|
+
'.dsh-remote-meta.json',
|
|
106
|
+
'.dsh-remote-sync-state.json',
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extra directories skipped by `rw_search` (but NOT by mirror sync — a user may
|
|
111
|
+
* legitimately want their cache tree mirrored, and silently excluding it from
|
|
112
|
+
* sync would lose data).
|
|
113
|
+
*
|
|
114
|
+
* These are machine-local package/tool caches: enormous numbers of tiny files
|
|
115
|
+
* that essentially never contain what a human is searching for, and that are
|
|
116
|
+
* the main way a "search my home directory" turns into a multi-hour walk
|
|
117
|
+
* (issue #44 reports `~/.npm/_cacache` specifically). Searching them is
|
|
118
|
+
* available via an explicit `path`, which bypasses these excludes.
|
|
119
|
+
*/
|
|
120
|
+
export const DEFAULT_SEARCH_IGNORE = [
|
|
121
|
+
...DEFAULT_IGNORE,
|
|
122
|
+
'.npm/',
|
|
123
|
+
'.cache/',
|
|
124
|
+
'.local/',
|
|
125
|
+
'.cargo/',
|
|
126
|
+
'.rustup/',
|
|
127
|
+
'.gradle/',
|
|
128
|
+
'.m2/',
|
|
129
|
+
'.pnpm-store/',
|
|
130
|
+
'.yarn/',
|
|
131
|
+
'.conda/',
|
|
132
|
+
'.docker/',
|
|
133
|
+
'.nvm/',
|
|
134
|
+
'.pyenv/',
|
|
135
|
+
'.Trash/',
|
|
136
|
+
'.Trash-1000/',
|
|
137
|
+
'snap/',
|
|
138
|
+
'AppData/',
|
|
139
|
+
'Library/Caches/',
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether a normalized remote path is a FILESYSTEM ROOT: POSIX `/`, a Windows
|
|
144
|
+
* drive root (`D:\`), or a UNC share root (`\\server\share`).
|
|
145
|
+
*
|
|
146
|
+
* Root workspaces are legal (issue #60) but dangerous to walk naively: `/proc`
|
|
147
|
+
* and `/sys` are synthetic (new pids appear between readdirs, `/proc/kcore`
|
|
148
|
+
* advertises 128 TB), `/dev` holds never-ending character devices, and
|
|
149
|
+
* `/var/lib/docker` is tens of GB of image layers. ROOT_IGNORE exists so a
|
|
150
|
+
* root workspace skips exactly those trees.
|
|
151
|
+
*/
|
|
152
|
+
export function isFilesystemRoot(p) {
|
|
153
|
+
const s = String(p || '')
|
|
154
|
+
return s === '/' || /^[a-zA-Z]:\\?$/.test(s) || /^\\\\[^\\]+\\[^\\]+$/.test(s)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Patterns merged in ONLY when the workspace is the filesystem root
|
|
159
|
+
* (issue #60). Every entry is root-anchored AND directory-only, so even when
|
|
160
|
+
* merged they can only match at the workspace top level: `/` skips `/proc`,
|
|
161
|
+
* while a hypothetical `/home/u/proc` under a non-root workspace is ordinary
|
|
162
|
+
* content (and non-root workspaces never receive these patterns at all, so
|
|
163
|
+
* their behaviour is byte-identical to before).
|
|
164
|
+
*/
|
|
165
|
+
export const ROOT_IGNORE = [
|
|
166
|
+
'/proc/',
|
|
167
|
+
'/sys/',
|
|
168
|
+
'/dev/',
|
|
169
|
+
'/run/',
|
|
170
|
+
'/boot/',
|
|
171
|
+
'/var/lib/docker/',
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Effective ignore patterns for a workspace rooted at `root`:
|
|
176
|
+
* the defaults, plus ROOT_IGNORE only for a root workspace, plus the user's
|
|
177
|
+
* own `.dsh-remote-ignore` lines (always last, they stack on top).
|
|
178
|
+
*
|
|
179
|
+
* @param {string} root - workspace root as passed to sync/push/search.
|
|
180
|
+
* @param {{search?: boolean, fromFile?: string[]}} [opts]
|
|
181
|
+
*/
|
|
182
|
+
export function ignorePatternsFor(root, { search = false, fromFile = [] } = {}) {
|
|
183
|
+
const base = search ? DEFAULT_SEARCH_IGNORE : DEFAULT_IGNORE
|
|
184
|
+
const extra = isFilesystemRoot(root) ? ROOT_IGNORE : []
|
|
185
|
+
return base.concat(extra, fromFile)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load the user ignore file (gitignore syntax) and merge with defaults.
|
|
190
|
+
* Returns `{ patterns, fromFile }`.
|
|
191
|
+
*/
|
|
192
|
+
export function loadIgnore(ignoreFile) {
|
|
193
|
+
let fromFile = []
|
|
194
|
+
if (ignoreFile) {
|
|
195
|
+
try {
|
|
196
|
+
const text = readFileSync(ignoreFile, 'utf8')
|
|
197
|
+
fromFile = text.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'))
|
|
198
|
+
} catch { /* missing file → defaults only */ }
|
|
199
|
+
}
|
|
200
|
+
const patterns = DEFAULT_IGNORE.concat(fromFile)
|
|
201
|
+
return { patterns, fromFile, matcher: compileIgnore(patterns) }
|
|
202
|
+
}
|