|
1 | | -#!/usr/bin/env -S pkgx --quiet deno^2.1 run --ext=ts --allow-sys=uid --allow-run --allow-env=PKGX_DIR,HOMEBREW_PREFIX,HOME,PATH --allow-read --allow-write |
2 | | -import { SemVer, semver } from "https://deno.land/x/libpkgx@v0.20.3/mod.ts"; |
| 1 | +#!/usr/bin/env -S pkgx --quiet deno^2.1 run --ext=ts --allow-sys=uid --allow-run --allow-env --allow-read --allow-write --allow-ffi |
| 2 | +import { |
| 3 | + Path, |
| 4 | + SemVer, |
| 5 | + semver, |
| 6 | +} from "https://deno.land/x/libpkgx@v0.20.3/mod.ts"; |
3 | 7 | import { dirname, fromFileUrl, join } from "jsr:@std/path@^1"; |
4 | | -import { ensureDir, existsSync } from "jsr:@std/fs@^1"; |
| 8 | +import { ensureDir, existsSync, walk } from "jsr:@std/fs@^1"; |
5 | 9 | import { parseArgs } from "jsr:@std/cli@^1"; |
6 | 10 |
|
7 | 11 | function standardPath() { |
@@ -58,24 +62,28 @@ if (parsedArgs.help) { |
58 | 62 | case "li": |
59 | 63 | await install(args, `${Deno.env.get("HOME")!}/.local`); |
60 | 64 | break; |
| 65 | + case "stub": |
61 | 66 | case "shim": |
62 | 67 | // this uses the old behavior of pkgx v1, which is to install to ~/.local/bin |
63 | 68 | // if we want to write to /usr/local, we need to use sudo |
64 | 69 | await shim(args, `${Deno.env.get("HOME")!}/.local`); |
65 | | - // await shim(args, "/usr/local"); |
66 | 70 | break; |
67 | | - // case "local-shim": |
68 | | - // await shim(args, `${Deno.env.get("HOME")!}/.local`); |
69 | | - // break; |
70 | 71 | case "uninstall": |
71 | 72 | case "rm": |
| 73 | + for (const arg of args) { |
| 74 | + await uninstall(arg); |
| 75 | + } |
| 76 | + break; |
72 | 77 | case "list": |
73 | 78 | case "ls": |
| 79 | + for await (const path of ls()) { |
| 80 | + console.log(path); |
| 81 | + } |
| 82 | + break; |
74 | 83 | case "up": |
75 | 84 | case "update": |
76 | 85 | case "pin": |
77 | 86 | case "outdated": |
78 | | - case "stub": |
79 | 87 | console.error("%cunimplemented. soz. U EARLY.", "color: red"); |
80 | 88 | Deno.exit(1); |
81 | 89 | break; |
@@ -422,3 +430,119 @@ function get_pkgx() { |
422 | 430 | } |
423 | 431 | throw new Error("no `pkgx` found in `$PATH`"); |
424 | 432 | } |
| 433 | + |
| 434 | +async function* ls() { |
| 435 | + for ( |
| 436 | + const path of [new Path("/usr/local/pkgs"), Path.home().join(".local/pkgs")] |
| 437 | + ) { |
| 438 | + if (!path.isDirectory()) continue; |
| 439 | + const dirs = [path]; |
| 440 | + let dir: Path | undefined; |
| 441 | + while ((dir = dirs.pop()) != undefined) { |
| 442 | + for await (const [path, { name, isDirectory, isSymlink }] of dir.ls()) { |
| 443 | + if (!isDirectory || isSymlink) continue; |
| 444 | + if (/^v\d+\./.test(name)) { |
| 445 | + yield path; |
| 446 | + } else { |
| 447 | + dirs.push(path); |
| 448 | + } |
| 449 | + } |
| 450 | + } |
| 451 | + } |
| 452 | +} |
| 453 | + |
| 454 | +import { hooks, plumbing } from "https://deno.land/x/libpkgx@v0.20.3/mod.ts"; |
| 455 | + |
| 456 | +async function uninstall(arg: string) { |
| 457 | + let found: { project: string } | undefined = |
| 458 | + (await hooks.usePantry().find(arg))?.[0]; |
| 459 | + if (!found) { |
| 460 | + found = await plumbing.which(arg); |
| 461 | + } |
| 462 | + if (!found) throw new Error(`pkg not found: ${arg}`); |
| 463 | + |
| 464 | + const set = new Set<string>(); |
| 465 | + const files: Path[] = []; |
| 466 | + let dirs: Path[] = []; |
| 467 | + const pkg_dirs: Path[] = []; |
| 468 | + for (const root of [new Path("/usr/local"), Path.home().join(".local")]) { |
| 469 | + const dir = root.join("pkgs", found.project).isDirectory(); |
| 470 | + if (!dir) continue; |
| 471 | + pkg_dirs.push(dir); |
| 472 | + for await (const [pkgdir, { isDirectory }] of dir.ls()) { |
| 473 | + if (!isDirectory) continue; |
| 474 | + for await (const { path, isDirectory } of walk(pkgdir.string)) { |
| 475 | + const leaf = new Path(path).relative({ to: pkgdir }); |
| 476 | + const resolved_path = root.join(leaf); |
| 477 | + if (set.has(resolved_path.string)) continue; |
| 478 | + if (!resolved_path.exists()) continue; |
| 479 | + if (isDirectory) { |
| 480 | + dirs.push(resolved_path); |
| 481 | + } else { |
| 482 | + files.push(resolved_path); |
| 483 | + } |
| 484 | + } |
| 485 | + } |
| 486 | + } |
| 487 | + |
| 488 | + // we need to delete this in a heirachical fashion or they don’t delete |
| 489 | + dirs = dirs.sort().reverse(); |
| 490 | + |
| 491 | + if (files.length == 0) { |
| 492 | + console.error("not installed"); |
| 493 | + Deno.exit(1); |
| 494 | + } |
| 495 | + |
| 496 | + const needs_sudo = files.some((p) => p.string.startsWith("/usr/local")); |
| 497 | + if (needs_sudo) { |
| 498 | + { |
| 499 | + const { success, code } = await new Deno.Command("/usr/bin/sudo", { |
| 500 | + args: ["rm", ...files.map((p) => p.string)], |
| 501 | + }).spawn().status; |
| 502 | + if (!success) Deno.exit(code); |
| 503 | + } |
| 504 | + { |
| 505 | + await new Deno.Command("/usr/bin/sudo", { |
| 506 | + args: ["rmdir", ...dirs.map((p) => p.string)], |
| 507 | + stderr: "null", |
| 508 | + }).spawn().status; |
| 509 | + } |
| 510 | + |
| 511 | + const { success, code } = await new Deno.Command("/usr/bin/sudo", { |
| 512 | + args: [ |
| 513 | + "rm", |
| 514 | + "-rf", |
| 515 | + ...pkg_dirs.map((p) => p.string), |
| 516 | + ...pkg_dirs.map((x) => x.parent().string), |
| 517 | + ], |
| 518 | + }).spawn().status; |
| 519 | + if (!success) Deno.exit(code); |
| 520 | + |
| 521 | + await new Deno.Command("/usr/bin/sudo", { |
| 522 | + args: [ |
| 523 | + "rmdir", |
| 524 | + "/usr/local/pkgs", |
| 525 | + Path.home().join(".local/pkgs").string, |
| 526 | + ], |
| 527 | + stderr: "null", |
| 528 | + }).spawn().status; |
| 529 | + } else { |
| 530 | + for (const path of files) { |
| 531 | + if (!path.isDirectory()) { |
| 532 | + Deno.removeSync(path.string); |
| 533 | + } |
| 534 | + } |
| 535 | + for (const path of dirs) { |
| 536 | + if (path.isDirectory()) { |
| 537 | + try { |
| 538 | + Deno.removeSync(path.string); |
| 539 | + } catch { |
| 540 | + // some dirs will not be removable |
| 541 | + } |
| 542 | + } |
| 543 | + } |
| 544 | + for (const path of pkg_dirs) { |
| 545 | + Deno.removeSync(path.string, { recursive: true }); |
| 546 | + } |
| 547 | + } |
| 548 | +} |
0 commit comments