shilo-cli 0.11.0 → 0.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shilo-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log, diff, sync y propuestas.",
5
5
  "keywords": [
6
6
  "shilo",
@@ -9,6 +9,7 @@ const sync = require('../sync');
9
9
  const prompt = require('../prompt');
10
10
  const textdiff = require('../textdiff');
11
11
  const { resolveContext } = require('../context');
12
+ const { puedeAplicar } = require('../viewer');
12
13
 
13
14
  /**
14
15
  * shilo propuestas [ver|bajar|aplicar|descartar] [id] [opciones]
@@ -147,7 +148,7 @@ async function ver(ctx, { proposal: resumen, viewer }, args) {
147
148
  chequeos: d.chequeos,
148
149
  usee: d.usee,
149
150
  blocked_by: d.blockedBy,
150
- puedo_aplicar: !!(viewer && viewer.is_owner),
151
+ puedo_aplicar: puedeAplicar(viewer),
151
152
  changes: d.changes.map((c) => ({
152
153
  ...c,
153
154
  ...(args.contenido ? { contenido_base: texto(c.base), contenido_propuesta: texto(c.head) } : {}),
@@ -222,7 +223,7 @@ async function ver(ctx, { proposal: resumen, viewer }, args) {
222
223
  ui.info('');
223
224
  const id8 = p.id.slice(0, 8);
224
225
  if (!args.diff) ui.info(ui.c.dim(`Diferencias: shilo propuestas ver ${id8} --diff`));
225
- if (viewer && viewer.is_owner) {
226
+ if (puedeAplicar(viewer)) {
226
227
  ui.info(ui.c.dim(d.conflicts.length > 0
227
228
  ? `Resolver: shilo propuestas aplicar ${id8} --propuesta <ruta> --actual <ruta> (o --todo propuesta)`
228
229
  : `Aplicar: shilo propuestas aplicar ${id8}`));
@@ -362,8 +363,8 @@ async function aplicar(ctx, { proposal: resumen, viewer }, args) {
362
363
  ui.error(`Esa propuesta ya está ${resumen.state}.`);
363
364
  return 1;
364
365
  }
365
- if (viewer && !viewer.is_owner) {
366
- ui.error('Sólo el dueño del proyecto puede aplicar una propuesta.');
366
+ if (viewer && !puedeAplicar(viewer)) {
367
+ ui.error('Para aplicar una propuesta hace falta ser el dueño o tener el permiso de aprobar propuestas.');
367
368
  return 1;
368
369
  }
369
370
 
@@ -22,6 +22,7 @@ const {
22
22
  MAX_FILE_BYTES,
23
23
  } = require('../textguard');
24
24
  const { ShiloClient } = require('../ShiloClient');
25
+ const { puedeAplicar } = require('../viewer');
25
26
 
26
27
  const DEFAULT_BASE_URL = 'https://www.shilo.city';
27
28
  const COMMIT_BATCH = 100; // máximo de archivos por commit (límite del backend, camino anterior)
@@ -1412,11 +1413,12 @@ async function push(args) {
1412
1413
 
1413
1414
  config.persistCredsIfChanged(cfg, mgr.current);
1414
1415
 
1415
- // 9b) Si quien sube es el dueño, lo que quedó abierto lo puede aplicar ahora
1416
- // mismo en vez de ir a la web a aprobarse a sí mismo.
1416
+ // 9b) Si quien sube puede aprobar (dueño o permiso `approve_proposals`), lo
1417
+ // que quedó abierto lo puede aplicar ahora mismo en vez de ir a la web a
1418
+ // aprobarse a sí mismo.
1417
1419
  const failures = failedWrites.length + plan.failed.length;
1418
1420
  let openShown = false;
1419
- if (open.length > 0 && failures === 0 && viewer && viewer.is_owner && !args.yes && process.stdin.isTTY) {
1421
+ if (open.length > 0 && failures === 0 && puedeAplicar(viewer) && !args.yes && process.stdin.isTTY) {
1420
1422
  const res = await offerOwnerApply(open);
1421
1423
  if (res.applied.length > 0) {
1422
1424
  committed += res.applied.reduce((n, o) => n + o.paths.length, 0);
@@ -1492,7 +1494,7 @@ async function push(args) {
1492
1494
  const pendingFiles = open.reduce((n, o) => n + o.paths.length, 0);
1493
1495
  const owner = projectInfo && projectInfo.owner;
1494
1496
  const ownerName = owner ? owner.display_name || owner.username : null;
1495
- const approver = viewer && viewer.is_owner ? 'tu aprobación' : ownerName ? `la aprobación de ${ownerName}` : 'la aprobación del dueño del proyecto';
1497
+ const approver = puedeAplicar(viewer) ? 'tu aprobación' : ownerName ? `la aprobación de ${ownerName}` : 'la aprobación del dueño del proyecto';
1496
1498
  ui.success(
1497
1499
  `Tus ${pendingFiles} cambio(s) están guardados en Shilo y esperan ${approver}.` +
1498
1500
  (committed > 0 ? ` (Otros ${committed} ya son parte de la versión actual.)` : '')
@@ -1516,7 +1518,8 @@ async function push(args) {
1516
1518
  ui.info('');
1517
1519
  for (const o of list) reportOpen(o);
1518
1520
  openShown = true;
1519
- ui.info(`Sos el dueño del proyecto: podés aplicar estos ${files} cambio(s) ahora, sin pasar por la web.`);
1521
+ const quien = viewer && viewer.is_owner ? 'Sos el dueño del proyecto' : 'Tenés permiso de aprobar propuestas';
1522
+ ui.info(`${quien}: podés aplicar estos ${files} cambio(s) ahora, sin pasar por la web.`);
1520
1523
  if (conflicted > 0) {
1521
1524
  ui.info(
1522
1525
  `En ${conflicted} archivo(s) que también cambiaron en el proyecto se va a quedar ` +
@@ -5,6 +5,7 @@ const ui = require('../ui');
5
5
  const sync = require('../sync');
6
6
  const remote = require('../remote');
7
7
  const { resolveContext } = require('../context');
8
+ const { puedeAplicar } = require('../viewer');
8
9
 
9
10
  // A partir de cuántos días sin sincronizar se recuerda hacerlo.
10
11
  const STALE_DAYS = 30;
@@ -126,11 +127,11 @@ async function status(args) {
126
127
  };
127
128
  };
128
129
 
129
- // 4b) Propuestas abiertas: las mías (esperan aprobación) y, si soy el dueño,
130
- // las de los demás (esperan mi decisión).
130
+ // 4b) Propuestas abiertas: las mías (esperan aprobación) y, si puedo aprobar
131
+ // (dueño o permiso `approve_proposals`), las de los demás.
131
132
  const openProposals = listed ? listed.proposals : [];
132
133
  const mineOpen = me ? openProposals.filter((p) => p.author_id === me) : [];
133
- const othersOpen = viewer && viewer.is_owner ? openProposals.filter((p) => p.author_id !== me) : [];
134
+ const othersOpen = puedeAplicar(viewer) ? openProposals.filter((p) => p.author_id !== me) : [];
134
135
  const projectInfo = (listed && listed.project) || null;
135
136
  const owner = projectInfo && projectInfo.owner;
136
137
  const ownerName = owner ? owner.display_name || owner.username : null;
@@ -217,7 +218,7 @@ async function status(args) {
217
218
  /** Propuestas abiertas: lo primero que hay que saber, antes que la lista de archivos. */
218
219
  const reportProposals = () => {
219
220
  if (mineOpen.length > 0) {
220
- const who = viewer && viewer.is_owner ? 'tu aprobación' : ownerName ? `la aprobación de ${ownerName}` : 'aprobación';
221
+ const who = puedeAplicar(viewer) ? 'tu aprobación' : ownerName ? `la aprobación de ${ownerName}` : 'aprobación';
221
222
  ui.info(ui.c.bold(`Tenés ${mineOpen.length} propuesta(s) esperando ${who}:`));
222
223
  for (const p of mineOpen) {
223
224
  const link = linkTo(p);
package/src/viewer.js ADDED
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Si quien pregunta puede aplicar propuestas en el proyecto: el dueño o un
5
+ * colaborador con el permiso `approve_proposals` (migración 021).
6
+ *
7
+ * Lo dice el servidor en `viewer.can_apply`. Un servidor anterior a la 021 no
8
+ * lo manda y sólo el dueño podía aplicar, así que sin el campo se usa
9
+ * `is_owner`. Es sólo para mostrar u ofrecer: el servidor decide igual.
10
+ */
11
+ function puedeAplicar(viewer) {
12
+ if (!viewer) return false;
13
+ if (typeof viewer.can_apply === 'boolean') return viewer.can_apply;
14
+ return !!viewer.is_owner;
15
+ }
16
+
17
+ module.exports = { puedeAplicar };