shilo-cli 0.9.0 → 0.10.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 CHANGED
@@ -186,6 +186,41 @@ shilo deploys -n 20 --json
186
186
  `--deploy` sólo agrega el trailer `[deploy: …]` al mensaje; escribirlo a mano en
187
187
  `-m` da lo mismo.
188
188
 
189
+ ### Respaldar todas las carpetas (sync)
190
+
191
+ ```bash
192
+ shilo sync # lista las carpetas conectadas y su último sync
193
+ shilo sync --all # sube todas, una tras otra
194
+ shilo sync ./boda # sólo esa carpeta
195
+ shilo sync a4c2 # o por id de proyecto (alcanza un prefijo)
196
+ shilo sync --scan ~/proyectos # registra las carpetas ya conectadas que haya ahí
197
+ shilo sync --forget ./vieja # la saca del registro (no toca el disco)
198
+ shilo sync --all --dry-run # qué subiría cada una, sin subir nada
199
+ ```
200
+
201
+ El CLI lleva un registro de carpetas conectadas en `~/.shilo/carpetas.json`. Se llena
202
+ solo: cada `init`, `push` o `pull` registra su carpeta. Las que se conectaron antes
203
+ de esta versión se suman una vez con `--scan`, que busca `.shilo.json` hasta 6 niveles
204
+ abajo (no entra en `node_modules`, `.git` ni `dist`).
205
+
206
+ Sincronizar es **subir**: cada carpeta pasa por `shilo push --yes`, con sus mismas
207
+ garantías (un conflicto queda como propuesta abierta, una carpeta con posibles
208
+ secretos no sube nada). Cada push corre en su propio proceso, así que si una carpeta
209
+ falla las demás siguen, y al final hay un resumen. **Un respaldo no despliega:**
210
+ manda `--deploy off` salvo que pases otro `--deploy`.
211
+
212
+ **Una carpeta sin `base` se saltea.** `base` es la versión del proyecto sobre la que
213
+ está tu copia; la escriben `push` y `pull`. Sin ella, un push no puede saber si tu
214
+ copia está vieja y subiría tus archivos como cambios, pisando lo más nuevo. Esas
215
+ carpetas aparecen como "requiere atención": revisalas con `shilo status` y
216
+ `shilo diff`, subilas a mano una vez con `shilo push` (o bajá lo nuevo con
217
+ `shilo pull`) y desde ahí entran solas al respaldo.
218
+
219
+ Exit code 1 = alguna carpeta falló o se salteó. Una propuesta que queda esperando
220
+ aprobación (exit 3 de `push`) cuenta como respaldada. El plan de las etapas
221
+ siguientes (respaldo programado, vista en la web) está en `PLAN_SINCRONIZAR.md` en la
222
+ raíz del repo.
223
+
189
224
  ### Diferencias (diff)
190
225
 
191
226
  ```bash
@@ -326,6 +361,8 @@ SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natura
326
361
  `registerBlob` para binarios, camino viejo sólo como reserva.
327
362
  - **Fase 8 — deploys** *(hecho)*: `push --deploy <prod|preview|off>` y
328
363
  `shilo deploys` para el deploy automático a Vercel (PLAN_DEPLOY_VERCEL).
364
+ - **Fase 9 — respaldo** *(Etapa 1 hecha)*: registro de carpetas conectadas y
365
+ `shilo sync [--all]`. Lo que sigue, en `PLAN_SINCRONIZAR.md`.
329
366
 
330
367
  ## Exit codes
331
368
 
@@ -335,4 +372,5 @@ SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natura
335
372
  | `diff` | 1 | Hay diferencias entre local y remoto. |
336
373
  | `pull` | 1 | Quedaron conflictos sin bajar (o fallas). |
337
374
  | `push` | 1 | Se detectaron posibles secretos (nada subido). |
375
+ | `sync` | 1 | Alguna carpeta no se respaldó (falló o se salteó). |
338
376
  | cualquiera | 1 | Error. |
package/bin/shilo.js CHANGED
@@ -25,7 +25,8 @@ ${ui.c.bold('Comandos:')}
25
25
  review [versión] Quién revisó una versión, o dejar tu revisión.
26
26
  export <carpeta> Escribe la historia del proyecto como un repositorio Git.
27
27
  deploys Destino e historial de deploys a Vercel del proyecto.
28
- help Muestra esta ayuda.
28
+ sync [carpeta|uuid] Respalda las carpetas conectadas (--all: todas).
29
+ help Muestra esta ayuda.
29
30
 
30
31
  ${ui.c.bold('Opciones de login:')}
31
32
  --email <e> Email (si se omite, se pregunta).
@@ -117,6 +118,18 @@ ${ui.c.bold('Opciones de deploys:')}
117
118
  --json Salida JSON.
118
119
  --project <uuid> Proyecto (pisa el .shilo.json de la carpeta).
119
120
 
121
+ ${ui.c.bold('Opciones de sync:')}
122
+ (sin argumentos) Lista las carpetas conectadas y su último sync.
123
+ --all Sube todas las carpetas registradas, una tras otra.
124
+ --scan [raíz] Registra las carpetas con .shilo.json bajo raíz (default: la actual).
125
+ --forget <carpeta> La saca del registro (no toca nada en disco).
126
+ -m, --message <m> Mensaje de los commits (default: "Respaldo (shilo sync) <fecha>").
127
+ --deploy <d> prod | preview | off (default off: un respaldo no despliega).
128
+ --dry-run Muestra qué subiría cada carpeta, sin subir nada.
129
+ ${ui.c.dim('Se registran solas con init, push o pull. Una carpeta sin "base" en su')}
130
+ ${ui.c.dim('.shilo.json se saltea: subila a mano una vez con "shilo push".')}
131
+ ${ui.c.dim('Exit code 1 = alguna carpeta falló o se salteó.')}
132
+
120
133
  ${ui.c.bold('Globales:')}
121
134
  -v, --version Versión del CLI.
122
135
  -h, --help Ayuda.
@@ -135,6 +148,7 @@ const COMMANDS = {
135
148
  review: () => require('../src/commands/review'),
136
149
  export: () => require('../src/commands/export'),
137
150
  deploys: () => require('../src/commands/deploys'),
151
+ sync: () => require('../src/commands/sync'),
138
152
  };
139
153
 
140
154
  async function main() {
@@ -146,7 +160,7 @@ async function main() {
146
160
  booleans: [
147
161
  'help', 'version', 'h', 'v', 'dry-run', 'yes', 'force', 'push', 'pull',
148
162
  'json', 'short', 'stat', 'files', 'allow-secrets', 'no-index', 'verbose',
149
- 'allow-failing-checks',
163
+ 'allow-failing-checks', 'all',
150
164
  'show-ignored', 'include-ignored', 'aprobado', 'reparos', 'rechazado',
151
165
  ],
152
166
  aliases: { h: 'help', v: 'version', m: 'message' },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shilo-cli",
3
- "version": "0.9.0",
4
- "description": "CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log y diff.",
3
+ "version": "0.10.0",
4
+ "description": "CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log, diff y sync.",
5
5
  "keywords": [
6
6
  "shilo",
7
7
  "cli",
@@ -25,7 +25,8 @@
25
25
  "README.md"
26
26
  ],
27
27
  "scripts": {
28
- "start": "node bin/shilo.js"
28
+ "start": "node bin/shilo.js",
29
+ "test": "node --test"
29
30
  },
30
31
  "engines": {
31
32
  "node": ">=18"
@@ -0,0 +1,240 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `shilo sync` — respaldo de las carpetas conectadas (PLAN_SINCRONIZAR, Etapa 1).
5
+ *
6
+ * shilo sync lista las carpetas registradas y su último sync
7
+ * shilo sync --all sube todas, una tras otra
8
+ * shilo sync <carpeta|uuid> sube sólo esa (por ruta o por id de proyecto)
9
+ * shilo sync --scan [raíz] registra las carpetas atadas que haya bajo raíz
10
+ * shilo sync --forget <dir> saca una carpeta del registro (no toca el disco)
11
+ *
12
+ * Sincronizar es SUBIR: cada carpeta pasa por `shilo push --yes`, con todas sus
13
+ * garantías — un conflicto queda como propuesta abierta sin pisar a nadie, y una
14
+ * carpeta con posibles secretos no sube nada. Cada push corre en su propio
15
+ * proceso: lo que falle en una carpeta no arrastra a las demás.
16
+ *
17
+ * Una carpeta sin `base` en su `.shilo.json` se saltea (ver `checkFolder`).
18
+ *
19
+ * Un respaldo no despliega: se manda `--deploy off` salvo que se pida otra cosa,
20
+ * porque con `auto_target = production` un respaldo nocturno iría a producción.
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const { spawn } = require('child_process');
26
+ const folders = require('../folders');
27
+ const remote = require('../remote');
28
+ const ui = require('../ui');
29
+
30
+ const BIN = path.join(__dirname, '..', '..', 'bin', 'shilo.js');
31
+ // Un prefijo del id alcanza (como un hash corto de git), pero no menos de 4.
32
+ const UUID_RE = /^[0-9a-f-]{4,36}$/i;
33
+
34
+ async function sync(args) {
35
+ if (args.forget !== undefined) return forget(args.forget);
36
+ if (args.scan !== undefined) return scan(args.scan === true ? '.' : args.scan);
37
+
38
+ let entries;
39
+ try {
40
+ entries = folders.load();
41
+ } catch (e) {
42
+ ui.error(e.message);
43
+ return 1;
44
+ }
45
+
46
+ const target = args._[0];
47
+ if (!args.all && !target) return list(entries);
48
+
49
+ let selected;
50
+ if (args.all) {
51
+ selected = entries;
52
+ } else {
53
+ selected = resolveTarget(target, entries);
54
+ if (!selected) return 1;
55
+ }
56
+
57
+ if (selected.length === 0) {
58
+ ui.warn('No hay carpetas registradas.');
59
+ ui.info('Corré "shilo sync --scan <carpeta>" para encontrar las que ya están conectadas.');
60
+ return 0;
61
+ }
62
+
63
+ const deploy = typeof args.deploy === 'string' ? args.deploy : 'off';
64
+ const message =
65
+ typeof args.message === 'string' && args.message.trim()
66
+ ? args.message.trim()
67
+ : `Respaldo (shilo sync) ${new Date().toLocaleString('es-AR')}`;
68
+
69
+ const results = [];
70
+ for (const [i, entry] of selected.entries()) {
71
+ ui.info('');
72
+ ui.info(ui.c.bold(`[${i + 1}/${selected.length}] ${entry.dir}`));
73
+
74
+ const check = checkFolder(entry);
75
+ if (check.problem) {
76
+ ui.warn(check.problem);
77
+ results.push({ dir: entry.dir, status: 'salteada', detail: check.problem });
78
+ continue;
79
+ }
80
+
81
+ const pushArgs = ['push', entry.dir, '--yes', '-m', message, '--deploy', deploy];
82
+ if (args['dry-run']) pushArgs.push('--dry-run');
83
+ const code = await runChild(pushArgs);
84
+ results.push(resultFor(entry.dir, code));
85
+ }
86
+
87
+ return summarize(results, !!args['dry-run']);
88
+ }
89
+
90
+ /** Código de salida de `push` → fila del resumen. */
91
+ function resultFor(dir, code) {
92
+ if (code === 0) return { dir, status: 'ok', detail: '' };
93
+ // 3 = todo guardado, pero el proyecto exige aprobación: el respaldo está hecho.
94
+ if (code === 3) return { dir, status: 'ok', detail: 'guardado; la propuesta espera aprobación' };
95
+ return { dir, status: 'falló', detail: `push salió con código ${code} (ver arriba)` };
96
+ }
97
+
98
+ /** Lista el registro con el estado que dice cada `.shilo.json`. */
99
+ function list(entries) {
100
+ if (entries.length === 0) {
101
+ ui.info('No hay carpetas registradas todavía.');
102
+ ui.info(
103
+ ui.c.dim(
104
+ 'Se registran solas con init, push o pull; las ya conectadas se suman con "shilo sync --scan <carpeta>".'
105
+ )
106
+ );
107
+ return 0;
108
+ }
109
+ ui.info(ui.c.bold(`Carpetas conectadas (${entries.length}):`));
110
+ for (const entry of entries) {
111
+ const check = checkFolder(entry);
112
+ const when = check.remote && check.remote.lastSync
113
+ ? new Date(check.remote.lastSync).toLocaleString('es-AR')
114
+ : 'nunca';
115
+ const state = check.problem ? ui.c.yellow(check.problem) : ui.c.dim(`último sync: ${when}`);
116
+ ui.info(` ${entry.dir}`);
117
+ ui.info(` ${ui.c.dim(entry.projectId || '?')} ${state}`);
118
+ }
119
+ ui.info('');
120
+ ui.info(ui.c.dim('"shilo sync --all" las sube todas; "shilo sync <carpeta|uuid>" sólo una.'));
121
+ return 0;
122
+ }
123
+
124
+ /**
125
+ * `<carpeta|uuid>` → lista de una entrada. Una carpeta existente que no estaba
126
+ * en el registro se registra (si está atada).
127
+ */
128
+ function resolveTarget(target, entries) {
129
+ const abs = path.resolve(target);
130
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
131
+ const r = safeRead(abs);
132
+ if (!r || !r.projectId) {
133
+ ui.error(`La carpeta no está conectada a Shilo (no tiene .shilo.json): ${abs}`);
134
+ return null;
135
+ }
136
+ folders.add(abs, r.projectId);
137
+ return [{ dir: abs, projectId: r.projectId }];
138
+ }
139
+
140
+ if (UUID_RE.test(target)) {
141
+ const t = target.toLowerCase();
142
+ const matches = entries.filter((e) => e.projectId && e.projectId.toLowerCase().startsWith(t));
143
+ if (matches.length > 0) return matches; // un proyecto atado en dos carpetas: las dos
144
+ }
145
+
146
+ ui.error(`No encontré "${target}" ni como carpeta ni como proyecto registrado.`);
147
+ ui.info('Corré "shilo sync" para ver la lista.');
148
+ return null;
149
+ }
150
+
151
+ /** ¿Se puede sincronizar? Devuelve `{ remote, problem }`. */
152
+ function checkFolder(entry) {
153
+ if (!fs.existsSync(entry.dir)) {
154
+ return { remote: null, problem: 'la carpeta ya no existe ("shilo sync --forget" para sacarla)' };
155
+ }
156
+ const r = safeRead(entry.dir);
157
+ if (!r || !r.projectId) {
158
+ return { remote: null, problem: 'ya no tiene .shilo.json (¿se desconectó?)' };
159
+ }
160
+ if (r.projectId !== entry.projectId) folders.addQuietly(entry.dir, r.projectId);
161
+ // Sin `base`, el push toma el main actual como base y trata como cambios
162
+ // todos los archivos locales que difieren: una copia vieja pisaría trabajo
163
+ // más nuevo (pasó el 2026-10-12). Eso no puede pasar sin que alguien mire.
164
+ if (!r.base) return { remote: r, problem: NO_BASE };
165
+ return { remote: r, problem: null };
166
+ }
167
+
168
+ const NO_BASE =
169
+ 'requiere atención: sin "base" en .shilo.json no sé si está al día. Revisala con ' +
170
+ '"shilo status" y "shilo diff", y subila a mano una vez con "shilo push" (o "shilo pull" si está atrasada)';
171
+
172
+ function safeRead(dir) {
173
+ try {
174
+ return remote.read(dir);
175
+ } catch (_) {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ function scan(root) {
181
+ const abs = path.resolve(root);
182
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
183
+ ui.error(`No es una carpeta válida: ${abs}`);
184
+ return 1;
185
+ }
186
+ ui.info(ui.c.dim(`Buscando carpetas conectadas bajo ${abs}…`));
187
+ const found = folders.scan(abs);
188
+ let added = 0;
189
+ for (const f of found) {
190
+ const isNew = folders.add(f.dir, f.projectId);
191
+ if (isNew) added++;
192
+ ui.info(` ${isNew ? ui.c.green('+') : ui.c.dim('=')} ${f.dir}`);
193
+ }
194
+ if (found.length === 0) ui.info('No encontré ninguna carpeta con .shilo.json.');
195
+ else ui.success(`${found.length} encontrada(s), ${added} nueva(s) en el registro.`);
196
+ return 0;
197
+ }
198
+
199
+ function forget(dir) {
200
+ if (dir === true) {
201
+ ui.error('Falta la carpeta: shilo sync --forget <carpeta>');
202
+ return 1;
203
+ }
204
+ if (folders.remove(dir)) {
205
+ ui.success(`Sacada del registro: ${path.resolve(dir)}`);
206
+ ui.info(ui.c.dim('No se tocó nada en disco; un push o pull futuro la vuelve a registrar.'));
207
+ return 0;
208
+ }
209
+ ui.warn(`No estaba en el registro: ${path.resolve(dir)}`);
210
+ return 0;
211
+ }
212
+
213
+ function runChild(argv) {
214
+ return new Promise((resolve) => {
215
+ const child = spawn(process.execPath, [BIN, ...argv], { stdio: 'inherit' });
216
+ child.on('error', (e) => {
217
+ ui.error(`No se pudo lanzar el push: ${e.message}`);
218
+ resolve(1);
219
+ });
220
+ child.on('close', (code) => resolve(typeof code === 'number' ? code : 1));
221
+ });
222
+ }
223
+
224
+ function summarize(results, dryRun) {
225
+ ui.info('');
226
+ ui.info(ui.c.bold('Resumen del respaldo:'));
227
+ for (const r of results) {
228
+ const mark =
229
+ r.status === 'ok' ? ui.c.green('✓') : r.status === 'salteada' ? ui.c.yellow('!') : ui.c.red('✗');
230
+ ui.info(` ${mark} ${r.dir}${r.detail ? ui.c.dim(` — ${r.detail}`) : ''}`);
231
+ }
232
+ const ok = results.filter((r) => r.status === 'ok').length;
233
+ const bad = results.length - ok;
234
+ const verb = dryRun ? 'revisada(s) (--dry-run: no se subió nada)' : 'respaldada(s)';
235
+ if (bad === 0) ui.success(`${ok} carpeta(s) ${verb}.`);
236
+ else ui.warn(`${ok} ${verb}, ${bad} con problemas.`);
237
+ return bad === 0 ? 0 : 1;
238
+ }
239
+
240
+ module.exports = sync;
package/src/folders.js ADDED
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Registro local de carpetas conectadas: `~/.shilo/carpetas.json`.
5
+ *
6
+ * El servidor conoce los proyectos, pero no en qué carpetas de qué máquina
7
+ * está atado cada uno: eso sólo lo sabe el disco (cada `.shilo.json`). Este
8
+ * registro es la lista que `shilo sync --all` recorre (PLAN_SINCRONIZAR, Etapa 1).
9
+ *
10
+ * Se llena solo: `remote.write` —el único lugar donde se escribe un
11
+ * `.shilo.json`, lo usen `init`, `pull` o `push`— registra la carpeta. Las que
12
+ * se ataron antes de que existiera el registro se suman con `shilo sync --scan`.
13
+ *
14
+ * Estructura en disco:
15
+ * {
16
+ * "version": 1,
17
+ * "carpetas": [
18
+ * { "dir": "C:\\...\\boda", "projectId": "<uuid>", "addedAt": "<ISO>" }
19
+ * ]
20
+ * }
21
+ *
22
+ * No guarda credenciales ni el estado de sincronización: el estado vive en el
23
+ * `.shilo.json` de cada carpeta (`lastSync`), que es la fuente de verdad.
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const config = require('./config');
29
+ const remote = require('./remote');
30
+
31
+ const FILENAME = 'carpetas.json';
32
+
33
+ // Lo que `--scan` no recorre: pesado y nunca contiene una carpeta atada propia.
34
+ const SCAN_SKIP = new Set(['node_modules', '.git', '.shilo', '.vercel', 'dist', 'build']);
35
+
36
+ function registryPath() {
37
+ return path.join(config.configDir(), FILENAME);
38
+ }
39
+
40
+ /** En Windows y macOS las rutas no distinguen mayúsculas: una carpeta, una entrada. */
41
+ function keyOf(dir) {
42
+ const abs = path.resolve(dir);
43
+ return process.platform === 'linux' ? abs : abs.toLowerCase();
44
+ }
45
+
46
+ function load() {
47
+ const p = registryPath();
48
+ if (!fs.existsSync(p)) return [];
49
+ let raw;
50
+ try {
51
+ raw = JSON.parse(fs.readFileSync(p, 'utf8').replace(/^/, ''));
52
+ } catch (e) {
53
+ throw new Error(`Registro de carpetas corrupto en ${p}: ${e.message}`);
54
+ }
55
+ return Array.isArray(raw.carpetas) ? raw.carpetas.filter((c) => c && typeof c.dir === 'string') : [];
56
+ }
57
+
58
+ function save(list) {
59
+ fs.mkdirSync(config.configDir(), { recursive: true });
60
+ const p = registryPath();
61
+ fs.writeFileSync(p, JSON.stringify({ version: 1, carpetas: list }, null, 2) + '\n', 'utf8');
62
+ return p;
63
+ }
64
+
65
+ /**
66
+ * Registra (o actualiza el proyecto de) una carpeta. Devuelve `true` si era nueva.
67
+ */
68
+ function add(dir, projectId) {
69
+ const abs = path.resolve(dir);
70
+ const list = load();
71
+ const k = keyOf(abs);
72
+ const found = list.find((c) => keyOf(c.dir) === k);
73
+ if (found) {
74
+ if (projectId && found.projectId !== projectId) {
75
+ found.projectId = projectId;
76
+ save(list);
77
+ }
78
+ return false;
79
+ }
80
+ list.push({ dir: abs, projectId: projectId || null, addedAt: new Date().toISOString() });
81
+ save(list);
82
+ return true;
83
+ }
84
+
85
+ /**
86
+ * Versión para `remote.write`: registrar nunca puede romper un push o un pull
87
+ * (config de sólo lectura, registro corrupto, etc.).
88
+ */
89
+ function addQuietly(dir, projectId) {
90
+ try {
91
+ add(dir, projectId);
92
+ } catch (_) {
93
+ /* el registro es una comodidad, no un requisito */
94
+ }
95
+ }
96
+
97
+ /** Saca una carpeta del registro. Devuelve `true` si estaba. */
98
+ function remove(dir) {
99
+ const k = keyOf(dir);
100
+ const list = load();
101
+ const rest = list.filter((c) => keyOf(c.dir) !== k);
102
+ if (rest.length === list.length) return false;
103
+ save(rest);
104
+ return true;
105
+ }
106
+
107
+ /**
108
+ * Busca `.shilo.json` bajo `root` (incluida) hasta `maxDepth` niveles.
109
+ * No entra en una carpeta atada: un repo de Shilo no contiene otro.
110
+ * Devuelve `[{ dir, projectId }]`.
111
+ */
112
+ function scan(root, { maxDepth = 6 } = {}) {
113
+ const found = [];
114
+ const walk = (dir, depth) => {
115
+ let info = null;
116
+ try {
117
+ info = remote.read(dir);
118
+ } catch (_) {
119
+ /* `.shilo.json` corrupto: no es una carpeta sincronizable */
120
+ }
121
+ if (info && info.projectId) {
122
+ found.push({ dir, projectId: info.projectId });
123
+ return;
124
+ }
125
+ if (depth >= maxDepth) return;
126
+ let entries;
127
+ try {
128
+ entries = fs.readdirSync(dir, { withFileTypes: true });
129
+ } catch (_) {
130
+ return; // sin permiso de lectura
131
+ }
132
+ for (const e of entries) {
133
+ if (!e.isDirectory() || SCAN_SKIP.has(e.name)) continue;
134
+ walk(path.join(dir, e.name), depth + 1);
135
+ }
136
+ };
137
+ walk(path.resolve(root), 0);
138
+ return found;
139
+ }
140
+
141
+ module.exports = { registryPath, load, add, addQuietly, remove, scan, keyOf };
package/src/remote.js CHANGED
@@ -72,6 +72,9 @@ function write(dir, { projectId, baseURL, lastSync, base }) {
72
72
  if (lastSync) onDisk.lastSync = lastSync;
73
73
  if (base) onDisk.base = base;
74
74
  fs.writeFileSync(p, JSON.stringify(onDisk, null, 2) + '\n', 'utf8');
75
+ // Toda carpeta atada queda en el registro de `shilo sync --all`. Require
76
+ // perezoso: `folders` ya depende de este módulo.
77
+ require('./folders').addQuietly(dir, projectId);
75
78
  return p;
76
79
  }
77
80