@gorlitzer-labs/seldon 0.1.6 → 0.1.8
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/bin/seldon.js +97 -1
- package/package.json +1 -1
package/bin/seldon.js
CHANGED
|
@@ -194,6 +194,90 @@ function doInstall(ids, { dev } = {}) {
|
|
|
194
194
|
console.log("\n" + C.bold("Done. ") + C.green(good.join(", ") || "—") + (bad.length ? " " + C.red("failed: " + bad.join(", ")) : ""));
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// ---- uninstall --------------------------------------------------------------
|
|
198
|
+
// Remove a node CLI from the global store. `rm -g` is the same verb for pnpm,
|
|
199
|
+
// npm and bun; try the chosen PM first, then any other present (a tool may have
|
|
200
|
+
// been installed under a different one). Removing an absent package is success.
|
|
201
|
+
function uninstallNpm(m) {
|
|
202
|
+
const pm = nodePM();
|
|
203
|
+
const tried = new Set();
|
|
204
|
+
for (const cand of [pm, "pnpm", "npm", "bun"]) {
|
|
205
|
+
if (tried.has(cand) || !has(cand)) continue;
|
|
206
|
+
tried.add(cand);
|
|
207
|
+
if (run(cand, ["rm", "-g", m.pkg])) return true;
|
|
208
|
+
}
|
|
209
|
+
console.log(C.dim(` ${m.id} not found in any global store (already gone)`));
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// demerzel & co: the isolated venv lives under ~/.seldon/<id>. Just delete it.
|
|
214
|
+
function uninstallPython(m) {
|
|
215
|
+
const dest = path.join(process.env.HOME, ".seldon", m.id);
|
|
216
|
+
if (fs.existsSync(dest)) { fs.rmSync(dest, { recursive: true, force: true }); console.log(C.dim(` removed ${dest}`)); }
|
|
217
|
+
else console.log(C.dim(` ${m.id} not present`));
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// bifrost: a single script in ~/bin or ~/.local/bin, plus ~/.config/bifrost.
|
|
222
|
+
function uninstallShell(m) {
|
|
223
|
+
let removed = false;
|
|
224
|
+
for (const p of [path.join(process.env.HOME, "bin", m.bin), path.join(process.env.HOME, ".local", "bin", m.bin)]) {
|
|
225
|
+
if (fs.existsSync(p)) { fs.rmSync(p, { force: true }); console.log(C.dim(` removed ${p}`)); removed = true; }
|
|
226
|
+
}
|
|
227
|
+
const cfg = path.join(process.env.HOME, ".config", m.id);
|
|
228
|
+
if (fs.existsSync(cfg)) { fs.rmSync(cfg, { recursive: true, force: true }); console.log(C.dim(` removed ${cfg}`)); removed = true; }
|
|
229
|
+
if (!removed) console.log(C.dim(` ${m.id} not present`));
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function uninstallOne(m) {
|
|
234
|
+
console.log("\n" + C.bold(`▸ ${m.id}`) + C.dim(` — remove`));
|
|
235
|
+
const fn = { npm: uninstallNpm, shell: uninstallShell, python: uninstallPython }[m.method];
|
|
236
|
+
const ok = fn(m);
|
|
237
|
+
console.log(ok ? C.green(` ✓ ${m.id} removed`) : C.red(` ✗ ${m.id} failed`));
|
|
238
|
+
return ok;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A single y/N prompt. Built on the same raw-keypress mechanism as the picker
|
|
242
|
+
// (proven to release stdin and let the process exit) rather than
|
|
243
|
+
// readline.createInterface, whose stdin stayed entangled and hung after abort.
|
|
244
|
+
// One keypress: 'y' = yes, anything else (incl. Ctrl-C) = no.
|
|
245
|
+
function confirm(question) {
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
if (!process.stdin.isTTY) return resolve(true); // non-interactive: assume yes (paired with an explicit ids/--all)
|
|
248
|
+
process.stdout.write(question);
|
|
249
|
+
readline.emitKeypressEvents(process.stdin);
|
|
250
|
+
process.stdin.setRawMode(true);
|
|
251
|
+
process.stdin.resume();
|
|
252
|
+
const onKey = (str, key) => {
|
|
253
|
+
process.stdin.off("keypress", onKey);
|
|
254
|
+
process.stdin.setRawMode(false);
|
|
255
|
+
process.stdin.pause();
|
|
256
|
+
process.stdin.unref();
|
|
257
|
+
process.stdout.write((str || "") + "\n");
|
|
258
|
+
resolve(/^y$/i.test(str || "") && !(key && key.ctrl));
|
|
259
|
+
};
|
|
260
|
+
process.stdin.on("keypress", onKey);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function doUninstall(ids, { yes = false, all = false } = {}) {
|
|
265
|
+
const targets = all ? MODULES.map((m) => m.id) : ids;
|
|
266
|
+
if (!targets.length) { console.log(C.red("nothing to uninstall — name modules or use `seldon uninstall --all`.")); process.exitCode = 1; return; }
|
|
267
|
+
console.log(C.bold("\nUninstall: ") + targets.join(", "));
|
|
268
|
+
console.log(C.dim(" removes their global CLI / isolated venv — never your system Python, tmux, tailscale, etc."));
|
|
269
|
+
if (!yes && !(await confirm(C.gold("\nProceed? [y/N] ")))) { console.log(C.dim("aborted.")); return; }
|
|
270
|
+
const results = targets.map((id) => [id, uninstallOne(byId[id])]);
|
|
271
|
+
// A full uninstall also drops the isolated ~/.seldon (demerzel + the uv/CPython it bootstrapped).
|
|
272
|
+
if (all) {
|
|
273
|
+
const root = path.join(process.env.HOME, ".seldon");
|
|
274
|
+
if (fs.existsSync(root)) { fs.rmSync(root, { recursive: true, force: true }); console.log(C.dim(`\n removed ${root} (isolated uv/venv store)`)); }
|
|
275
|
+
}
|
|
276
|
+
const good = results.filter(([, r]) => r).map(([i]) => i);
|
|
277
|
+
const bad = results.filter(([, r]) => !r).map(([i]) => i);
|
|
278
|
+
console.log("\n" + C.bold("Done. ") + C.green("removed: " + (good.join(", ") || "—")) + (bad.length ? " " + C.red("failed: " + bad.join(", ")) : ""));
|
|
279
|
+
}
|
|
280
|
+
|
|
197
281
|
// ---- the checklist TUI ------------------------------------------------------
|
|
198
282
|
const BANNER = [
|
|
199
283
|
" ███████╗███████╗██╗ ██████╗ ██████╗ ███╗ ██╗",
|
|
@@ -242,6 +326,8 @@ function tui() {
|
|
|
242
326
|
const finish = (ids) => {
|
|
243
327
|
process.stdin.off("keypress", onKey);
|
|
244
328
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
329
|
+
process.stdin.pause(); // emitKeypressEvents resumed stdin; without this the
|
|
330
|
+
process.stdin.unref(); // event loop stays alive and the process hangs after install
|
|
245
331
|
readline.cursorTo(process.stdout, 0); readline.clearScreenDown(process.stdout);
|
|
246
332
|
resolve(ids);
|
|
247
333
|
};
|
|
@@ -264,10 +350,13 @@ ${C.bold("seldon")} — install the Seldon stack, pick what you use.
|
|
|
264
350
|
|
|
265
351
|
${C.bold("seldon")} open the checklist (space to pick, enter to install)
|
|
266
352
|
${C.bold("seldon install")} [ids…] install everything picked, or the named modules
|
|
353
|
+
${C.bold("seldon uninstall")} ids… remove the named modules (global CLI / isolated venv)
|
|
354
|
+
${C.bold("seldon uninstall --all")} remove the whole stack (also drops ~/.seldon)
|
|
267
355
|
${C.bold("seldon doctor")} [ids…] check external deps (tmux, sops, age, tailscale, python…)
|
|
268
356
|
${C.bold("seldon list")} list the modules
|
|
269
357
|
${C.bold("seldon --dev")} link node modules from this checkout instead of npm
|
|
270
358
|
${C.bold("seldon install --pm=pnpm")} force the node package manager (pnpm|bun|npm; auto-detected)
|
|
359
|
+
${C.dim("uninstall takes --yes to skip the confirm prompt.")}
|
|
271
360
|
|
|
272
361
|
${C.dim("node tools install via pnpm/bun if present (faster), else npm.")}
|
|
273
362
|
${C.dim("python (demerzel) installs into its own isolated venv — never your system Python;")}
|
|
@@ -281,7 +370,8 @@ const argv = process.argv.slice(2);
|
|
|
281
370
|
const dev = argv.includes("--dev");
|
|
282
371
|
const args = argv.filter((a) => !a.startsWith("--"));
|
|
283
372
|
const cmd = args[0];
|
|
284
|
-
const
|
|
373
|
+
const tokens = args.slice(1); // raw names after the command
|
|
374
|
+
const ids = tokens.filter((id) => byId[id]); // only the valid module ids
|
|
285
375
|
|
|
286
376
|
(async () => {
|
|
287
377
|
if (argv.includes("--help") || cmd === "help") return help();
|
|
@@ -291,6 +381,12 @@ const ids = args.slice(1).filter((id) => byId[id]);
|
|
|
291
381
|
if (ids.length) return doInstall(ids, { dev });
|
|
292
382
|
// install with no ids -> fall through to the picker
|
|
293
383
|
}
|
|
384
|
+
if (cmd === "uninstall") {
|
|
385
|
+
const all = argv.includes("--all");
|
|
386
|
+
const bad = tokens.filter((t) => !byId[t]);
|
|
387
|
+
if (bad.length) { console.log(C.red(`unknown module(s): ${bad.join(", ")}`)); console.log(C.dim(`modules: ${MODULES.map((m) => m.id).join(", ")}`)); process.exitCode = 1; return; }
|
|
388
|
+
return doUninstall(ids, { all, yes: argv.includes("--yes") });
|
|
389
|
+
}
|
|
294
390
|
if (cmd && cmd !== "install") { help(); process.exitCode = 1; return; }
|
|
295
391
|
// interactive
|
|
296
392
|
if (!process.stdin.isTTY) { console.log(C.red("no TTY — use: seldon install <ids…>")); process.exitCode = 1; return; }
|