sweeply 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/cli.js +166 -21
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  // src/cli.tsx
4
4
  import { render } from "ink";
5
- import os3 from "os";
5
+ import os4 from "os";
6
6
 
7
7
  // src/ui/App.tsx
8
8
  import { useEffect, useMemo, useState } from "react";
9
9
  import { Box as Box3, Text as Text3, useApp, useInput, useStdout } from "ink";
10
- import path3 from "path";
11
- import os2 from "os";
10
+ import path4 from "path";
11
+ import os3 from "os";
12
12
 
13
13
  // src/core/scanner.ts
14
14
  import fs from "fs/promises";
@@ -207,24 +207,141 @@ async function removeTargets(targets, onProgress) {
207
207
  return { removed, failed };
208
208
  }
209
209
 
210
+ // src/core/update.ts
211
+ import fs5 from "fs/promises";
212
+ import path3 from "path";
213
+ import os2 from "os";
214
+ import { fileURLToPath } from "url";
215
+ var PKG_NAME = "sweeply";
216
+ var REGISTRY = "https://registry.npmjs.org";
217
+ var CHECK_EVERY_MS = 24 * 60 * 60 * 1e3;
218
+ var FETCH_TIMEOUT_MS = 2e3;
219
+ async function readOwnVersion() {
220
+ let dir2 = path3.dirname(fileURLToPath(import.meta.url));
221
+ for (let i = 0; i < 5; i++) {
222
+ try {
223
+ const raw = await fs5.readFile(path3.join(dir2, "package.json"), "utf8");
224
+ const parsed = JSON.parse(raw);
225
+ if (parsed.name === PKG_NAME && parsed.version) return parsed.version;
226
+ } catch {
227
+ }
228
+ const parent = path3.dirname(dir2);
229
+ if (parent === dir2) break;
230
+ dir2 = parent;
231
+ }
232
+ return null;
233
+ }
234
+ function isNewer(latest, current) {
235
+ const nums = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
236
+ const a = nums(latest);
237
+ const b = nums(current);
238
+ for (let i = 0; i < 3; i++) {
239
+ const x = a[i] ?? 0;
240
+ const y = b[i] ?? 0;
241
+ if (x !== y) return x > y;
242
+ }
243
+ return false;
244
+ }
245
+ function upgradeCommand() {
246
+ const self = process.argv[1] ?? fileURLToPath(import.meta.url);
247
+ if (self.includes("/Caskroom/") || self.includes("/Cellar/")) {
248
+ return `brew upgrade ${PKG_NAME}`;
249
+ }
250
+ if (self.includes("/_npx/")) return `npx ${PKG_NAME}@latest`;
251
+ if (self.includes("/.bun/")) return `bun add -g ${PKG_NAME}@latest`;
252
+ if (self.includes("/yarn/global/")) return `yarn global upgrade ${PKG_NAME}`;
253
+ return `npm i -g ${PKG_NAME}@latest`;
254
+ }
255
+ function cacheFile() {
256
+ const base = process.env.XDG_CACHE_HOME || path3.join(os2.homedir(), ".cache");
257
+ return path3.join(base, PKG_NAME, "update-check.json");
258
+ }
259
+ async function readCache() {
260
+ try {
261
+ const raw = await fs5.readFile(cacheFile(), "utf8");
262
+ const parsed = JSON.parse(raw);
263
+ if (typeof parsed.at === "number" && typeof parsed.latest === "string") {
264
+ return { at: parsed.at, latest: parsed.latest };
265
+ }
266
+ } catch {
267
+ }
268
+ return null;
269
+ }
270
+ async function writeCache(latest) {
271
+ const file = cacheFile();
272
+ await fs5.mkdir(path3.dirname(file), { recursive: true });
273
+ await fs5.writeFile(file, JSON.stringify({ at: Date.now(), latest }), "utf8");
274
+ }
275
+ async function fetchLatest() {
276
+ const res = await fetch(`${REGISTRY}/${PKG_NAME}/latest`, {
277
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
278
+ headers: { accept: "application/json" }
279
+ });
280
+ if (!res.ok) return null;
281
+ const body = await res.json();
282
+ return body.version ?? null;
283
+ }
284
+ async function checkForUpdate() {
285
+ try {
286
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
287
+ const current = await readOwnVersion();
288
+ if (!current) return null;
289
+ const cached = await readCache();
290
+ let latest = null;
291
+ if (cached && Date.now() - cached.at < CHECK_EVERY_MS) {
292
+ latest = cached.latest;
293
+ } else {
294
+ latest = await fetchLatest();
295
+ if (latest) void writeCache(latest).catch(() => {
296
+ });
297
+ }
298
+ if (!latest || !isNewer(latest, current)) return null;
299
+ return { current, latest, command: upgradeCommand() };
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+
210
305
  // src/ui/ProjectRow.tsx
211
306
  import { Box, Text } from "ink";
212
307
  import { jsx, jsxs } from "react/jsx-runtime";
213
308
  var SEM_COLOR = { green: "green", yellow: "yellow", red: "red" };
214
- function ProjectRow({ row, isCursor }) {
309
+ var CURSOR_BG = "blackBright";
310
+ function ProjectRow({
311
+ row,
312
+ isCursor,
313
+ width
314
+ }) {
215
315
  const targets = row.project.targets.map((t) => t.name).join(" + ");
216
- const sem = row.git ? SEM_COLOR[row.git.semaphore] : "gray";
217
- return /* @__PURE__ */ jsxs(Box, { children: [
218
- /* @__PURE__ */ jsx(Text, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u276F " : " " }),
219
- /* @__PURE__ */ jsx(Text, { color: row.deleted ? "gray" : isCursor ? "cyan" : void 0, children: row.deleted ? "\u2714 " : row.selected ? "\u25A0 " : "\u25A1 " }),
220
- /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { bold: isCursor, strikethrough: row.deleted, wrap: "truncate", children: row.project.name }) }),
221
- /* @__PURE__ */ jsx(Box, { width: 9, justifyContent: "flex-end", marginRight: 2, children: /* @__PURE__ */ jsx(Text, { color: row.deleted ? "gray" : "magenta", children: row.deleted ? "liberado" : formatSize(row.size) }) }),
222
- /* @__PURE__ */ jsx(Box, { width: 16, children: /* @__PURE__ */ jsxs(Text, { color: sem, children: [
316
+ const bg = isCursor ? CURSOR_BG : void 0;
317
+ const dim = isCursor ? "white" : "gray";
318
+ const marked = row.selected && !row.deleted;
319
+ const danger = isCursor ? "redBright" : "red";
320
+ const sem = row.git ? SEM_COLOR[row.git.semaphore] : dim;
321
+ const nameColor = row.deleted ? dim : marked ? danger : void 0;
322
+ const sizeColor = row.deleted ? dim : marked ? danger : "magenta";
323
+ const tailColor = marked ? danger : dim;
324
+ return /* @__PURE__ */ jsxs(Box, { width, backgroundColor: bg, overflow: "hidden", children: [
325
+ /* @__PURE__ */ jsx(Text, { color: marked ? danger : "cyan", backgroundColor: bg, bold: marked, children: isCursor ? "\u258C" : marked ? "\u2503" : " " }),
326
+ /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: row.deleted ? dim : marked ? danger : void 0, bold: marked, children: row.deleted ? " \u2714 " : marked ? " \u2717 " : " \u25A1 " }),
327
+ /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(
328
+ Text,
329
+ {
330
+ backgroundColor: bg,
331
+ color: nameColor,
332
+ bold: isCursor || marked,
333
+ strikethrough: row.deleted,
334
+ wrap: "truncate",
335
+ children: row.project.name
336
+ }
337
+ ) }),
338
+ /* @__PURE__ */ jsx(Box, { width: 9, justifyContent: "flex-end", marginRight: 2, children: /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: sizeColor, bold: marked, children: row.deleted ? "liberado" : formatSize(row.size) }) }),
339
+ /* @__PURE__ */ jsx(Box, { width: 16, children: /* @__PURE__ */ jsxs(Text, { backgroundColor: bg, color: sem, bold: marked, children: [
223
340
  "\u25CF ",
224
341
  row.git ? row.git.summary : "\u2026"
225
342
  ] }) }),
226
- /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: row.git?.lastActivity ?? "" }) }),
227
- /* @__PURE__ */ jsx(Text, { color: "gray", wrap: "truncate", children: targets })
343
+ /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: tailColor, children: row.git?.lastActivity ?? "" }) }),
344
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, minWidth: 0, children: /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: tailColor, wrap: "truncate", children: targets }) })
228
345
  ] });
229
346
  }
230
347
 
@@ -244,12 +361,12 @@ function Footer({
244
361
  ] }),
245
362
  /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
246
363
  "\u2191\u2193 navegar \xB7 espacio seleccionar \xB7 a seleccionar \u{1F7E2} \xB7 enter borrar",
247
- selectedCount > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
248
- " (",
364
+ selectedCount > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "red", bold: true, children: [
365
+ " ",
366
+ "\u2717 ",
249
367
  selectedCount,
250
- " = ",
251
- formatSize(selectedSize),
252
- ")"
368
+ " marcados = ",
369
+ formatSize(selectedSize)
253
370
  ] }),
254
371
  " \xB7 g detalle git \xB7 s orden",
255
372
  sortBySize ? " (tama\xF1o)" : "",
@@ -271,6 +388,16 @@ function App({ rootDir, targets }) {
271
388
  const [sortBySize, setSortBySize] = useState(false);
272
389
  const [freedSize, setFreedSize] = useState(0);
273
390
  const [deleteProgress, setDeleteProgress] = useState("");
391
+ const [update, setUpdate] = useState(null);
392
+ useEffect(() => {
393
+ let cancelled = false;
394
+ void checkForUpdate().then((info) => {
395
+ if (!cancelled && info) setUpdate(info);
396
+ });
397
+ return () => {
398
+ cancelled = true;
399
+ };
400
+ }, []);
274
401
  const patchRow = (projectPath, patch) => {
275
402
  setRows(
276
403
  (prev) => prev.map((r) => r.project.path === projectPath ? { ...r, ...patch } : r)
@@ -343,10 +470,11 @@ function App({ rootDir, targets }) {
343
470
  else if (key.return && selected.length > 0) setMode("confirm");
344
471
  });
345
472
  const listHeight = Math.max(5, (stdout?.rows ?? 24) - 8);
473
+ const rowWidth = Math.max(20, (stdout?.columns ?? 80) - 1);
346
474
  const offset = Math.max(0, Math.min(cursor - Math.floor(listHeight / 2), visible.length - listHeight));
347
475
  const windowRows = visible.slice(offset, offset + listHeight);
348
476
  const totalFound = rows.reduce((a, r) => a + (r.size ?? 0), 0);
349
- const displayDir = path3.resolve(rootDir).replace(os2.homedir(), "~");
477
+ const displayDir = path4.resolve(rootDir).replace(os3.homedir(), "~");
350
478
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
351
479
  /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
352
480
  /* @__PURE__ */ jsx2(Text3, { bold: true, color: "cyan", children: "sweeply" }),
@@ -371,7 +499,15 @@ function App({ rootDir, targets }) {
371
499
  rootDir,
372
500
  "."
373
501
  ] }),
374
- windowRows.map((row, i) => /* @__PURE__ */ jsx2(ProjectRow, { row, isCursor: offset + i === cursor }, row.project.path)),
502
+ windowRows.map((row, i) => /* @__PURE__ */ jsx2(
503
+ ProjectRow,
504
+ {
505
+ row,
506
+ isCursor: offset + i === cursor,
507
+ width: rowWidth
508
+ },
509
+ row.project.path
510
+ )),
375
511
  showDetail && current?.git && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, children: [
376
512
  /* @__PURE__ */ jsx2(Text3, { bold: true, children: current.project.path }),
377
513
  current.git.detail.split("\n").map((line, i) => /* @__PURE__ */ jsx2(Text3, { color: "gray", children: line }, i))
@@ -402,6 +538,15 @@ function App({ rootDir, targets }) {
402
538
  "borrando\u2026 ",
403
539
  deleteProgress
404
540
  ] }) }),
541
+ update && /* @__PURE__ */ jsx2(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
542
+ "Hay una versi\xF3n nueva (",
543
+ update.current,
544
+ " \u2192 ",
545
+ update.latest,
546
+ "). Corre:",
547
+ " ",
548
+ /* @__PURE__ */ jsx2(Text3, { bold: true, children: update.command })
549
+ ] }) }),
405
550
  /* @__PURE__ */ jsx2(
406
551
  Footer,
407
552
  {
@@ -436,7 +581,7 @@ function buildTargets(extra) {
436
581
  // src/cli.tsx
437
582
  import { jsx as jsx3 } from "react/jsx-runtime";
438
583
  function parseArgs(argv) {
439
- let dir2 = os3.homedir();
584
+ let dir2 = os4.homedir();
440
585
  let extraTargets2 = [];
441
586
  for (let i = 0; i < argv.length; i++) {
442
587
  const arg = argv[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sweeply",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "TUI para encontrar y borrar peso muerto de tus proyectos (node_modules, .next, venv...) con semáforo de seguridad git",
5
5
  "license": "MIT",
6
6
  "author": "Fora (Ángel Iván López Delgado)",