shilo-cli 0.7.0 → 0.9.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 +338 -255
- package/bin/shilo.js +211 -165
- package/package.json +1 -1
- package/src/ShiloClient.js +1066 -837
- package/src/args.js +32 -8
- package/src/checks.js +57 -0
- package/src/commands/deploys.js +105 -0
- package/src/commands/diff.js +19 -3
- package/src/commands/export.js +226 -0
- package/src/commands/init.js +8 -0
- package/src/commands/log.js +4 -0
- package/src/commands/pull.js +86 -2
- package/src/commands/push.js +668 -69
- package/src/commands/review.js +101 -0
- package/src/commands/status.js +148 -14
- package/src/exportgit.js +417 -0
- package/src/gitobjects.js +532 -0
- package/src/projectconfig.js +76 -0
- package/src/safelist.js +101 -0
- package/src/secrets.js +75 -9
- package/src/sync.js +56 -5
- package/src/walk.js +30 -0
package/src/commands/pull.js
CHANGED
|
@@ -40,6 +40,38 @@ function readLocalBytes(absPath) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Carpeta de respaldo de esta corrida: `.shilo/respaldo/<ISO>/`.
|
|
45
|
+
*
|
|
46
|
+
* Va adentro de `.shilo/` —que está en DEFAULT_IGNORES— y no como un
|
|
47
|
+
* `<archivo>.local` al lado: un hermano se vuelve un archivo más del árbol y el
|
|
48
|
+
* push siguiente lo sube, o sea que cambiaríamos "perder trabajo" por "publicar
|
|
49
|
+
* tu versión en conflicto". Al ir por timestamp, dos pulls seguidos no se pisan.
|
|
50
|
+
*
|
|
51
|
+
* Los `:` del ISO no son válidos en nombres de archivo de Windows.
|
|
52
|
+
*/
|
|
53
|
+
function backupDirFor(dir, stamp) {
|
|
54
|
+
return path.join(dir, '.shilo', 'respaldo', stamp.replace(/:/g, '-'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Copia la versión local de `repoPath` al respaldo antes de pisarla. Devuelve
|
|
59
|
+
* true si quedó copiada. Es best-effort: si falla, el pull sigue —avisando—
|
|
60
|
+
* porque abortar a mitad del pool dejaría la carpeta peor que como estaba.
|
|
61
|
+
*/
|
|
62
|
+
function backupLocal(baseDir, absPath, repoPath) {
|
|
63
|
+
try {
|
|
64
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) return false;
|
|
65
|
+
const target = path.join(baseDir, ...repoPath.split('/'));
|
|
66
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
67
|
+
fs.copyFileSync(absPath, target);
|
|
68
|
+
return true;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
ui.warn(`no se pudo respaldar ${ui.c.dim(repoPath)} — ${e.message}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
43
75
|
/**
|
|
44
76
|
* shilo pull [carpeta] [--project <uuid>] [--base-url <url>] [--dry-run] [--yes] [--force]
|
|
45
77
|
*
|
|
@@ -101,14 +133,27 @@ async function pull(args) {
|
|
|
101
133
|
// devuelve una URL firmada en vez de contenido, y se bajan como bytes. Lo
|
|
102
134
|
// que se saltaba antes no era una limitación del formato sino del camino:
|
|
103
135
|
// el contenido viajaba como texto y un PNG volvía mutilado.
|
|
136
|
+
//
|
|
137
|
+
// Lo ignorado localmente tampoco se baja: si `.env.*` está en el ignore,
|
|
138
|
+
// que alguien haya subido un `.env.ejemplo` desde otra máquina no es razón
|
|
139
|
+
// para que aparezca acá. Además arregla el conteo: antes entraban en
|
|
140
|
+
// "nuevos" (walkLocal no los ve) y después se descartaban al descargar.
|
|
141
|
+
const includeIgnored = !!args['include-ignored'];
|
|
142
|
+
const isIgnoredPath = sync.makePathMatcher(dir);
|
|
143
|
+
|
|
104
144
|
const candidates = [];
|
|
105
145
|
const unsafe = [];
|
|
146
|
+
const ignoredRemote = [];
|
|
106
147
|
for (const f of tree.files) {
|
|
107
148
|
const safe = safeTarget(dir, f.repoPath);
|
|
108
149
|
if (!safe.ok) {
|
|
109
150
|
unsafe.push({ repoPath: f.repoPath, reason: safe.reason });
|
|
110
151
|
continue;
|
|
111
152
|
}
|
|
153
|
+
if (!includeIgnored && isIgnoredPath(f.repoPath)) {
|
|
154
|
+
ignoredRemote.push(f.repoPath);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
112
157
|
candidates.push({ ...f, target: safe.target, binary: !sync.isTextMime(f.mime_type) });
|
|
113
158
|
}
|
|
114
159
|
|
|
@@ -141,6 +186,9 @@ async function pull(args) {
|
|
|
141
186
|
` ${ui.c.dim('posiblemente iguales:')} ${maybeSame.length}\n` +
|
|
142
187
|
` ${ui.c.dim('sólo locales:')} ${onlyLocal.length} ${ui.c.dim('(el pull no los toca)')}` +
|
|
143
188
|
(binaryCount ? `\n ${ui.c.dim('binarios:')} ${binaryCount}` : '') +
|
|
189
|
+
(ignoredRemote.length
|
|
190
|
+
? `\n ${ui.c.dim('ignorados:')} ${ignoredRemote.length} ${ui.c.dim('(no se bajan)')}`
|
|
191
|
+
: '') +
|
|
144
192
|
(unsafe.length ? `\n ${ui.c.red('inseguros:')} ${unsafe.length}` : '')
|
|
145
193
|
);
|
|
146
194
|
ui.info('');
|
|
@@ -157,6 +205,9 @@ async function pull(args) {
|
|
|
157
205
|
);
|
|
158
206
|
}
|
|
159
207
|
for (const f of onlyLocal) ui.info(` ${ui.c.dim('·')} ${ui.c.dim(`${f.repoPath} (sólo local, se conserva)`)}`);
|
|
208
|
+
for (const p of ignoredRemote) {
|
|
209
|
+
ui.info(` ${ui.c.dim('·')} ${ui.c.dim(`${p} (ignorado localmente, no se baja)`)}`);
|
|
210
|
+
}
|
|
160
211
|
ui.info('');
|
|
161
212
|
if (conflicting.length > 0) {
|
|
162
213
|
ui.warn(`${conflicting.length} archivo(s) locales se sobrescribirían: hace falta --force.`);
|
|
@@ -204,9 +255,17 @@ async function pull(args) {
|
|
|
204
255
|
|
|
205
256
|
// 6) Descargar y escribir, con pool. Recién acá se sabe si un archivo del
|
|
206
257
|
// mismo tamaño era realmente idéntico: si lo era, no se reescribe.
|
|
258
|
+
// Antes de pisar un archivo que difería del remoto (sólo pasa con --force)
|
|
259
|
+
// se guarda la versión local bajo `.shilo/respaldo/<ISO>/`: recuperarse de
|
|
260
|
+
// una divergencia dejaba de depender de haber copiado a mano antes.
|
|
261
|
+
const conflictPaths = new Set(conflicting.map((f) => f.repoPath));
|
|
262
|
+
const backupStamp = new Date().toISOString();
|
|
263
|
+
const backupDir = backupDirFor(dir, backupStamp);
|
|
264
|
+
|
|
207
265
|
const written = [];
|
|
208
266
|
const unchanged = [];
|
|
209
267
|
const failed = [];
|
|
268
|
+
const backedUp = [];
|
|
210
269
|
const outcomes = await sync.runPool(toDownload, CONCURRENCY, async (f) => {
|
|
211
270
|
const creds = await mgr.valid();
|
|
212
271
|
const read = await client.readFile(projectId, f.repoPath, creds);
|
|
@@ -222,9 +281,13 @@ async function pull(args) {
|
|
|
222
281
|
return { repoPath: f.repoPath, skipped: true };
|
|
223
282
|
}
|
|
224
283
|
|
|
284
|
+
const saved = conflictPaths.has(f.repoPath)
|
|
285
|
+
? backupLocal(backupDir, f.target, f.repoPath)
|
|
286
|
+
: false;
|
|
287
|
+
|
|
225
288
|
fs.mkdirSync(path.dirname(f.target), { recursive: true });
|
|
226
289
|
fs.writeFileSync(f.target, bytes);
|
|
227
|
-
return { repoPath: f.repoPath, skipped: false };
|
|
290
|
+
return { repoPath: f.repoPath, skipped: false, backedUp: saved };
|
|
228
291
|
}
|
|
229
292
|
|
|
230
293
|
const content = read.file && read.file.content != null ? read.file.content : '';
|
|
@@ -234,9 +297,13 @@ async function pull(args) {
|
|
|
234
297
|
return { repoPath: f.repoPath, skipped: true };
|
|
235
298
|
}
|
|
236
299
|
|
|
300
|
+
const saved = conflictPaths.has(f.repoPath)
|
|
301
|
+
? backupLocal(backupDir, f.target, f.repoPath)
|
|
302
|
+
: false;
|
|
303
|
+
|
|
237
304
|
fs.mkdirSync(path.dirname(f.target), { recursive: true });
|
|
238
305
|
fs.writeFileSync(f.target, content, 'utf8');
|
|
239
|
-
return { repoPath: f.repoPath, skipped: false };
|
|
306
|
+
return { repoPath: f.repoPath, skipped: false, backedUp: saved };
|
|
240
307
|
});
|
|
241
308
|
|
|
242
309
|
for (const res of outcomes) {
|
|
@@ -247,6 +314,7 @@ async function pull(args) {
|
|
|
247
314
|
unchanged.push(res.repoPath);
|
|
248
315
|
} else {
|
|
249
316
|
written.push(res.repoPath);
|
|
317
|
+
if (res.backedUp) backedUp.push(res.repoPath);
|
|
250
318
|
ui.success(`${ui.c.green('↓')} ${res.repoPath}`);
|
|
251
319
|
}
|
|
252
320
|
}
|
|
@@ -271,6 +339,22 @@ async function pull(args) {
|
|
|
271
339
|
|
|
272
340
|
// 8) Resumen.
|
|
273
341
|
ui.info('');
|
|
342
|
+
if (backedUp.length > 0) {
|
|
343
|
+
ui.info(
|
|
344
|
+
`${backedUp.length} versión(es) local(es) sobrescrita(s) quedaron respaldadas en ` +
|
|
345
|
+
ui.c.cyan(path.relative(dir, backupDir).split(path.sep).join('/'))
|
|
346
|
+
);
|
|
347
|
+
ui.info(ui.c.dim('(está dentro de .shilo/, así que no se sube ni aparece en el status.)'));
|
|
348
|
+
ui.info('');
|
|
349
|
+
}
|
|
350
|
+
if (ignoredRemote.length > 0) {
|
|
351
|
+
ui.info(
|
|
352
|
+
ui.c.dim(
|
|
353
|
+
`${ignoredRemote.length} archivo(s) del remoto están ignorados localmente y no se bajaron ` +
|
|
354
|
+
`(--include-ignored para bajarlos igual).`
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
274
358
|
const tail = unchanged.length ? ` (${unchanged.length} ya estaban al día)` : '';
|
|
275
359
|
if (failed.length === 0) {
|
|
276
360
|
ui.success(`Pull completo: ${written.length} archivo(s) actualizados en "${dir}"${tail}.`);
|