shilo-cli 0.3.1 → 0.5.0
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 +74 -2
- package/bin/shilo.js +12 -5
- package/package.json +2 -2
- package/src/ShiloClient.js +134 -7
- package/src/args.js +4 -2
- package/src/commands/init.js +17 -6
- package/src/commands/login.js +14 -3
- package/src/commands/push.js +712 -165
- package/src/config.js +1 -1
- package/src/context.js +1 -1
- package/src/prompt.js +10 -2
- package/src/pushindex.js +151 -0
- package/src/remote.js +3 -2
- package/src/secrets.js +168 -0
- package/src/sync.js +86 -10
- package/src/textguard.js +44 -14
- package/src/token.js +72 -0
- package/src/ui.js +61 -1
- package/src/walk.js +142 -34
package/src/token.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolución del token de acceso personal (PAT).
|
|
5
|
+
*
|
|
6
|
+
* Un PAT pasado como `--token abc123` queda en el historial del shell, en la
|
|
7
|
+
* lista de procesos (`ps` lo muestra a cualquier usuario de la máquina) y en
|
|
8
|
+
* los logs de CI. Por eso se aceptan dos formas más discretas:
|
|
9
|
+
*
|
|
10
|
+
* --token - lee el token de stdin (`cat pat.txt | shilo login --token -`)
|
|
11
|
+
* SHILO_TOKEN=<pat> variable de entorno (lo natural en CI)
|
|
12
|
+
*
|
|
13
|
+
* La forma inline sigue funcionando —es la que se copia desde la web— pero
|
|
14
|
+
* avisa una vez.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const STDIN_SENTINEL = '-';
|
|
18
|
+
|
|
19
|
+
/** Lee stdin completo (hasta EOF) y devuelve la primera línea no vacía. */
|
|
20
|
+
function readStdin() {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
if (process.stdin.isTTY) {
|
|
23
|
+
reject(new Error('--token - espera el token por stdin, pero la entrada es una terminal.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
let data = '';
|
|
27
|
+
process.stdin.setEncoding('utf8');
|
|
28
|
+
process.stdin.on('data', (chunk) => {
|
|
29
|
+
data += chunk;
|
|
30
|
+
});
|
|
31
|
+
process.stdin.on('end', () => resolve(data));
|
|
32
|
+
process.stdin.on('error', reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function firstLine(text) {
|
|
37
|
+
for (const line of String(text).split(/\r?\n/)) {
|
|
38
|
+
const t = line.trim();
|
|
39
|
+
if (t) return t;
|
|
40
|
+
}
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Devuelve `{ token, source }` con source ∈ 'flag' | 'stdin' | 'env' | null.
|
|
46
|
+
* Lanza si `--token -` no encuentra nada que leer.
|
|
47
|
+
*/
|
|
48
|
+
async function resolveToken(args) {
|
|
49
|
+
const raw = typeof args.token === 'string' ? args.token.trim() : '';
|
|
50
|
+
|
|
51
|
+
if (raw === STDIN_SENTINEL) {
|
|
52
|
+
const token = firstLine(await readStdin());
|
|
53
|
+
if (!token) throw new Error('No llegó ningún token por stdin.');
|
|
54
|
+
return { token, source: 'stdin' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (raw) return { token: raw, source: 'flag' };
|
|
58
|
+
|
|
59
|
+
const env = (process.env.SHILO_TOKEN || '').trim();
|
|
60
|
+
if (env) return { token: env, source: 'env' };
|
|
61
|
+
|
|
62
|
+
return { token: '', source: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Aviso (una sola línea) cuando el token viajó de la forma más expuesta. */
|
|
66
|
+
function warnIfExposed(source, ui) {
|
|
67
|
+
if (source !== 'flag') return;
|
|
68
|
+
ui.warn('El token quedó en el historial del shell y en la lista de procesos.');
|
|
69
|
+
ui.info(ui.c.dim('Alternativas: SHILO_TOKEN=<pat> o `cat pat.txt | shilo login --token -`.'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { resolveToken, warnIfExposed, readStdin, firstLine, STDIN_SENTINEL };
|
package/src/ui.js
CHANGED
|
@@ -33,4 +33,64 @@ function error(msg) {
|
|
|
33
33
|
console.error(`${c.red('✗')} ${msg}`);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
/** ¿La salida es una terminal interactiva? (redirigida a archivo/CI: no) */
|
|
37
|
+
const isTTY = !!process.stdout.isTTY;
|
|
38
|
+
|
|
39
|
+
function formatEta(secs) {
|
|
40
|
+
if (!Number.isFinite(secs) || secs < 0) return '';
|
|
41
|
+
if (secs < 60) return `${Math.max(1, Math.round(secs))}s`;
|
|
42
|
+
const mins = Math.floor(secs / 60);
|
|
43
|
+
const rest = Math.round(secs % 60);
|
|
44
|
+
return `${mins}m ${rest}s`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Contador de progreso de una sola línea.
|
|
49
|
+
*
|
|
50
|
+
* En una terminal reescribe la misma línea con porcentaje y ETA; sin TTY no
|
|
51
|
+
* dibuja nada (una barra con `\r` en un log de CI es ruido ilegible) y en su
|
|
52
|
+
* lugar `line()` emite una línea por ítem. Así el mismo código sirve para el
|
|
53
|
+
* uso interactivo y para el registro.
|
|
54
|
+
*
|
|
55
|
+
* El repintado se limita a ~10 fps: con cientos de archivos, escribir en cada
|
|
56
|
+
* uno cuesta más que el trabajo que reporta.
|
|
57
|
+
*/
|
|
58
|
+
function progress(total, label = '') {
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
let lastLen = 0;
|
|
61
|
+
let lastDraw = 0;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
tty: isTTY,
|
|
65
|
+
update(done) {
|
|
66
|
+
if (!isTTY || total <= 0) return;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
if (done < total && now - lastDraw < 100) return;
|
|
69
|
+
lastDraw = now;
|
|
70
|
+
|
|
71
|
+
const pct = Math.round((done / total) * 100);
|
|
72
|
+
let eta = '';
|
|
73
|
+
if (done > 0 && done < total) {
|
|
74
|
+
const elapsed = (now - start) / 1000;
|
|
75
|
+
const left = formatEta((elapsed / done) * (total - done));
|
|
76
|
+
if (left) eta = `, faltan ~${left}`;
|
|
77
|
+
}
|
|
78
|
+
const line = ` ${label} ${done}/${total} (${pct}%${eta})`;
|
|
79
|
+
const pad = Math.max(0, lastLen - line.length);
|
|
80
|
+
process.stdout.write(`\r${c.dim(line)}${' '.repeat(pad)}`);
|
|
81
|
+
lastLen = line.length;
|
|
82
|
+
},
|
|
83
|
+
/** Borra la línea de progreso para que el mensaje siguiente arranque limpio. */
|
|
84
|
+
clear() {
|
|
85
|
+
if (!isTTY || lastLen === 0) return;
|
|
86
|
+
process.stdout.write(`\r${' '.repeat(lastLen)}\r`);
|
|
87
|
+
lastLen = 0;
|
|
88
|
+
},
|
|
89
|
+
/** Línea por ítem: sólo cuando no hay barra que la pisaría. */
|
|
90
|
+
line(msg) {
|
|
91
|
+
if (!isTTY) console.log(msg);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { c, info, success, warn, error, isTTY, progress, formatEta };
|
package/src/walk.js
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* Recorrido de un directorio local para el push.
|
|
5
5
|
*
|
|
6
6
|
* Responsabilidades (todo offline, sin red):
|
|
7
|
-
* - aplicar ignores por defecto (.git, node_modules, etc.) + `.
|
|
7
|
+
* - aplicar ignores por defecto (.git, node_modules, etc.) + `.gitignore` +
|
|
8
|
+
* `.shiloignore`, incluyendo los anidados de cada subcarpeta;
|
|
9
|
+
* - podar repositorios git detectados por su FORMA (HEAD + objects/ + refs/),
|
|
10
|
+
* no por el nombre `.git`: un `.git-ORIGINAL-BACKUP/` trae todo el historial
|
|
11
|
+
* y renombrarlo no lo vuelve inocuo;
|
|
12
|
+
* - descartar nombres reservados de Windows (`nul`, `con`, `com1`…), que en
|
|
13
|
+
* esa plataforma son dispositivos y no archivos;
|
|
8
14
|
* - descartar nombres inválidos para Shilo (poda subárboles con carpeta inválida);
|
|
9
15
|
* - respetar la profundidad máxima de carpetas;
|
|
10
16
|
* - NO leer el contenido completo (eso lo hace el push al clasificar binarios),
|
|
@@ -16,7 +22,7 @@
|
|
|
16
22
|
|
|
17
23
|
const fs = require('fs');
|
|
18
24
|
const path = require('path');
|
|
19
|
-
const { validateName, MAX_DEPTH } = require('./textguard');
|
|
25
|
+
const { validateName, MAX_DEPTH, WIN_RESERVED } = require('./textguard');
|
|
20
26
|
|
|
21
27
|
// Ignores por defecto (nombres de carpeta/archivo, a cualquier profundidad).
|
|
22
28
|
const DEFAULT_IGNORES = [
|
|
@@ -30,11 +36,40 @@ const DEFAULT_IGNORES = [
|
|
|
30
36
|
'.vercel/',
|
|
31
37
|
];
|
|
32
38
|
|
|
39
|
+
// Archivos de ignores que se leen en cada directorio, en orden de precedencia:
|
|
40
|
+
// `.shiloignore` va último para poder re-incluir con `!` algo que git ignora.
|
|
41
|
+
const IGNORE_FILES = ['.gitignore', '.shiloignore'];
|
|
42
|
+
|
|
43
|
+
// `WIN_RESERVED` (CON, NUL, COM1…) vive en textguard porque el backend también
|
|
44
|
+
// los rechaza. Acá se usan además para PODAR: abrir `nul` no devuelve un archivo
|
|
45
|
+
// sino el dispositivo nulo (`readFileSync` da vacío, `statSync` es inconsistente),
|
|
46
|
+
// así que se saltean en toda plataforma y no sólo en Windows.
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ¿Este directorio es un repositorio git (bare o no), sin importar cómo se llame?
|
|
50
|
+
* Un `.git-ORIGINAL-BACKUP/`, `.git.bak/` o un submódulo tienen la misma forma.
|
|
51
|
+
*/
|
|
52
|
+
function looksLikeGitDir(absDir) {
|
|
53
|
+
try {
|
|
54
|
+
return (
|
|
55
|
+
fs.existsSync(path.join(absDir, 'HEAD')) &&
|
|
56
|
+
fs.existsSync(path.join(absDir, 'objects')) &&
|
|
57
|
+
fs.existsSync(path.join(absDir, 'refs'))
|
|
58
|
+
);
|
|
59
|
+
} catch (_) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
/**
|
|
34
65
|
* Compila una línea de patrón estilo .gitignore (subconjunto práctico) a una
|
|
35
|
-
* regla { negate, dirOnly, anchored, re }.
|
|
66
|
+
* regla { negate, dirOnly, anchored, base, re }.
|
|
67
|
+
*
|
|
68
|
+
* `base` es el prefijo de ruta (POSIX, con `/` final) del directorio donde vive
|
|
69
|
+
* el archivo de ignores: una regla de `public/.gitignore` sólo aplica bajo
|
|
70
|
+
* `public/`.
|
|
36
71
|
*/
|
|
37
|
-
function compileRule(raw) {
|
|
72
|
+
function compileRule(raw, base = '') {
|
|
38
73
|
let line = raw.replace(/\r$/, '');
|
|
39
74
|
// Ignorar comentarios y líneas vacías.
|
|
40
75
|
if (!line.trim() || line.trim().startsWith('#')) return null;
|
|
@@ -63,8 +98,10 @@ function compileRule(raw) {
|
|
|
63
98
|
anchored = true;
|
|
64
99
|
}
|
|
65
100
|
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
// Los anclados se comparan contra la ruta completa, así que llevan el base
|
|
102
|
+
// incorporado; los de basename se filtran por `base` en el matcher.
|
|
103
|
+
const re = globToRegExp(anchored ? base + line : line);
|
|
104
|
+
return { negate, dirOnly, anchored, base, re };
|
|
68
105
|
}
|
|
69
106
|
|
|
70
107
|
function globToRegExp(glob) {
|
|
@@ -97,12 +134,14 @@ function globToRegExp(glob) {
|
|
|
97
134
|
*/
|
|
98
135
|
function makeMatcher(rules) {
|
|
99
136
|
return function isIgnored(relPath, isDir) {
|
|
100
|
-
const
|
|
137
|
+
const name = relPath.split('/').pop();
|
|
101
138
|
let ignored = false;
|
|
102
139
|
for (const rule of rules) {
|
|
103
140
|
if (!rule) continue;
|
|
104
141
|
if (rule.dirOnly && !isDir) continue;
|
|
105
|
-
|
|
142
|
+
// Una regla de un ignore anidado sólo alcanza a su propio subárbol.
|
|
143
|
+
if (rule.base && !relPath.startsWith(rule.base)) continue;
|
|
144
|
+
const target = rule.anchored ? relPath : name;
|
|
106
145
|
if (rule.re.test(target)) {
|
|
107
146
|
ignored = !rule.negate;
|
|
108
147
|
}
|
|
@@ -112,47 +151,86 @@ function makeMatcher(rules) {
|
|
|
112
151
|
}
|
|
113
152
|
|
|
114
153
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
154
|
+
* Compila las reglas de los archivos de ignores presentes en `absDir`.
|
|
155
|
+
* `relBase` es la ruta POSIX del directorio relativa a la raíz ('' para la raíz).
|
|
117
156
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
157
|
+
function loadDirRules(absDir, relBase = '') {
|
|
158
|
+
const base = relBase ? `${relBase}/` : '';
|
|
159
|
+
const rules = [];
|
|
160
|
+
for (const fileName of IGNORE_FILES) {
|
|
161
|
+
const ignorePath = path.join(absDir, fileName);
|
|
162
|
+
let text;
|
|
122
163
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
164
|
+
if (!fs.existsSync(ignorePath)) continue;
|
|
165
|
+
// Sin sacar el BOM (Notepad, PowerShell) el primer patrón del archivo se
|
|
166
|
+
// compila con un caracter invisible al frente y nunca matchea.
|
|
167
|
+
text = fs.readFileSync(ignorePath, 'utf8').replace(/^/, '');
|
|
125
168
|
} catch (_) {
|
|
126
|
-
|
|
169
|
+
continue; // si no se puede leer, seguimos con lo que haya
|
|
170
|
+
}
|
|
171
|
+
for (const line of text.split('\n')) {
|
|
172
|
+
const rule = compileRule(line, base);
|
|
173
|
+
if (rule) rules.push(rule);
|
|
127
174
|
}
|
|
128
175
|
}
|
|
129
|
-
return
|
|
176
|
+
return rules;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Reglas de la raíz: defaults + `.gitignore` + `.shiloignore` + extras.
|
|
181
|
+
* Se mantiene exportada porque es la forma de inspeccionar qué se va a ignorar
|
|
182
|
+
* sin recorrer nada.
|
|
183
|
+
*/
|
|
184
|
+
function loadIgnoreRules(rootDir, extraLines = []) {
|
|
185
|
+
const rules = [];
|
|
186
|
+
for (const line of DEFAULT_IGNORES) {
|
|
187
|
+
const rule = compileRule(line);
|
|
188
|
+
if (rule) rules.push(rule);
|
|
189
|
+
}
|
|
190
|
+
rules.push(...loadDirRules(rootDir, ''));
|
|
191
|
+
for (const line of extraLines) {
|
|
192
|
+
const rule = compileRule(line);
|
|
193
|
+
if (rule) rules.push(rule);
|
|
194
|
+
}
|
|
195
|
+
return rules;
|
|
130
196
|
}
|
|
131
197
|
|
|
132
198
|
/**
|
|
133
199
|
* Recorre `rootDir` y devuelve:
|
|
134
200
|
* {
|
|
135
|
-
* files: [{ relPath, absPath, size }],
|
|
136
|
-
* skipped: [{ relPath, reason }],
|
|
201
|
+
* files: [{ relPath, absPath, size }], // candidatos válidos a subir
|
|
202
|
+
* skipped: [{ relPath, reason, code }], // descartados
|
|
137
203
|
* ignored: <número de entradas ignoradas por reglas>
|
|
204
|
+
* gitDirs: [<rutas de repos git podados>]
|
|
138
205
|
* }
|
|
139
206
|
*
|
|
140
207
|
* No sigue symlinks (se reportan como skipped).
|
|
141
208
|
*/
|
|
142
209
|
function walk(rootDir, { extraIgnore = [] } = {}) {
|
|
143
|
-
const
|
|
144
|
-
const isIgnored = makeMatcher(rules);
|
|
210
|
+
const rootRules = loadIgnoreRules(rootDir, extraIgnore);
|
|
145
211
|
|
|
146
212
|
const files = [];
|
|
147
213
|
const skipped = [];
|
|
214
|
+
const gitDirs = [];
|
|
148
215
|
let ignored = 0;
|
|
149
216
|
|
|
150
|
-
function recurse(absDir, relBase, folderDepth) {
|
|
217
|
+
function recurse(absDir, relBase, folderDepth, inheritedRules) {
|
|
218
|
+
// Los ignores de este directorio se suman a los heredados y valen sólo de
|
|
219
|
+
// acá para abajo: por eso se copia el array en vez de mutarlo.
|
|
220
|
+
const rules = relBase
|
|
221
|
+
? inheritedRules.concat(loadDirRules(absDir, relBase))
|
|
222
|
+
: inheritedRules;
|
|
223
|
+
const isIgnored = makeMatcher(rules);
|
|
224
|
+
|
|
151
225
|
let entries;
|
|
152
226
|
try {
|
|
153
227
|
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
154
228
|
} catch (err) {
|
|
155
|
-
skipped.push({
|
|
229
|
+
skipped.push({
|
|
230
|
+
relPath: relBase || '.',
|
|
231
|
+
reason: `no se pudo leer: ${err.message}`,
|
|
232
|
+
code: 'unreadable',
|
|
233
|
+
});
|
|
156
234
|
return;
|
|
157
235
|
}
|
|
158
236
|
|
|
@@ -160,12 +238,24 @@ function walk(rootDir, { extraIgnore = [] } = {}) {
|
|
|
160
238
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
161
239
|
|
|
162
240
|
for (const entry of entries) {
|
|
163
|
-
|
|
241
|
+
// El nombre lógico (el que viaja al repo) se normaliza igual que en el
|
|
242
|
+
// backend —NFC y sin espacios al borde— porque es la forma que se guarda:
|
|
243
|
+
// macOS devuelve NFD, y sin esto "café.md" local y "café.md" remoto no se
|
|
244
|
+
// reconocen como el mismo archivo. `absPath` usa el nombre tal cual lo dio
|
|
245
|
+
// el SO, que es el que hay que abrir.
|
|
246
|
+
const logicalName = entry.name.normalize('NFC').trim();
|
|
247
|
+
const relPath = relBase ? `${relBase}/${logicalName}` : logicalName;
|
|
164
248
|
const absPath = path.join(absDir, entry.name);
|
|
165
249
|
const isDir = entry.isDirectory();
|
|
166
250
|
|
|
167
251
|
if (entry.isSymbolicLink()) {
|
|
168
|
-
skipped.push({ relPath, reason: 'symlink (no soportado)' });
|
|
252
|
+
skipped.push({ relPath, reason: 'symlink (no soportado)', code: 'symlink' });
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (WIN_RESERVED.test(entry.name)) {
|
|
257
|
+
// No es un archivo real en Windows; leerlo devolvería el dispositivo nulo.
|
|
258
|
+
ignored++;
|
|
169
259
|
continue;
|
|
170
260
|
}
|
|
171
261
|
|
|
@@ -174,40 +264,58 @@ function walk(rootDir, { extraIgnore = [] } = {}) {
|
|
|
174
264
|
continue;
|
|
175
265
|
}
|
|
176
266
|
|
|
177
|
-
|
|
267
|
+
if (isDir && looksLikeGitDir(absPath)) {
|
|
268
|
+
// Todo el historial del repo, incluidos secretos de commits ya purgados
|
|
269
|
+
// del árbol de trabajo. Se poda entero y se avisa una sola vez.
|
|
270
|
+
gitDirs.push(relPath);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const nameCheck = validateName(logicalName);
|
|
178
275
|
if (!nameCheck.ok) {
|
|
179
|
-
skipped.push({ relPath, reason: nameCheck.reason });
|
|
276
|
+
skipped.push({ relPath, reason: nameCheck.reason, code: 'name' });
|
|
180
277
|
continue; // si es carpeta, se poda todo el subárbol
|
|
181
278
|
}
|
|
182
279
|
|
|
183
280
|
if (isDir) {
|
|
184
281
|
if (folderDepth + 1 > MAX_DEPTH) {
|
|
185
|
-
skipped.push({
|
|
282
|
+
skipped.push({
|
|
283
|
+
relPath,
|
|
284
|
+
reason: `profundidad de carpetas > ${MAX_DEPTH}`,
|
|
285
|
+
code: 'depth',
|
|
286
|
+
});
|
|
186
287
|
continue;
|
|
187
288
|
}
|
|
188
|
-
recurse(absPath, relPath, folderDepth + 1);
|
|
289
|
+
recurse(absPath, relPath, folderDepth + 1, rules);
|
|
189
290
|
} else if (entry.isFile()) {
|
|
190
291
|
let size = 0;
|
|
292
|
+
let mtimeMs = 0;
|
|
191
293
|
try {
|
|
192
|
-
|
|
294
|
+
const st = fs.statSync(absPath);
|
|
295
|
+
size = st.size;
|
|
296
|
+
mtimeMs = st.mtimeMs;
|
|
193
297
|
} catch (_) {
|
|
194
298
|
/* si falla el stat, dejamos size en 0; el push lo detecta al leer */
|
|
195
299
|
}
|
|
196
|
-
files.push({ relPath, absPath, size });
|
|
300
|
+
files.push({ relPath, absPath, size, mtimeMs });
|
|
197
301
|
}
|
|
198
302
|
// otros tipos (fifo, socket, device) se ignoran silenciosamente
|
|
199
303
|
}
|
|
200
304
|
}
|
|
201
305
|
|
|
202
|
-
recurse(rootDir, '', 0);
|
|
203
|
-
return { files, skipped, ignored };
|
|
306
|
+
recurse(rootDir, '', 0, rootRules);
|
|
307
|
+
return { files, skipped, ignored, gitDirs };
|
|
204
308
|
}
|
|
205
309
|
|
|
206
310
|
module.exports = {
|
|
207
311
|
walk,
|
|
208
312
|
loadIgnoreRules,
|
|
313
|
+
loadDirRules,
|
|
209
314
|
makeMatcher,
|
|
210
315
|
compileRule,
|
|
211
316
|
globToRegExp,
|
|
317
|
+
looksLikeGitDir,
|
|
212
318
|
DEFAULT_IGNORES,
|
|
319
|
+
IGNORE_FILES,
|
|
320
|
+
WIN_RESERVED,
|
|
213
321
|
};
|