@nevermorelove/ompp 0.2.2 → 0.3.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 +3 -5
- package/ompp.js +166 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,12 +34,10 @@ ompp pentest --dry-run show the omp command line instead of running it
|
|
|
34
34
|
|
|
35
35
|
Before launch it prints one line to stderr, `[ompp] mode: pentest`, so you always know where you are.
|
|
36
36
|
|
|
37
|
-
Modes are read from
|
|
38
|
-
places win:
|
|
37
|
+
Modes are read from two places, and same-named modes from the first win:
|
|
39
38
|
|
|
40
|
-
1. `OMPP_MODES_DIR`, if you set it
|
|
41
|
-
2.
|
|
42
|
-
3. `~/.omp/ompp/modes/`, the user-level home for your own modes
|
|
39
|
+
1. `OMPP_MODES_DIR`, if you set it (a repo checkout, any custom folder)
|
|
40
|
+
2. `~/.omp/ompp/modes/`, the user-level home for your own modes
|
|
43
41
|
|
|
44
42
|
`ompp create` always writes to `~/.omp/ompp/modes/`, so you never edit files
|
|
45
43
|
inside an installed package. The first `ompp create` makes the folder for you.
|
package/ompp.js
CHANGED
|
@@ -19,19 +19,86 @@
|
|
|
19
19
|
const fs = require("fs");
|
|
20
20
|
const path = require("path");
|
|
21
21
|
const { spawn } = require("child_process");
|
|
22
|
-
|
|
23
22
|
const os = require("os");
|
|
23
|
+
const https = require("https");
|
|
24
|
+
// --- update check -----------------------------------------------------------
|
|
25
|
+
// One registry request per run, bounded by a 5s timeout. If the registry
|
|
26
|
+
// has a newer semver, print an upgrade banner.
|
|
27
|
+
function fetchLatestVersion() {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const req = https.get(
|
|
30
|
+
"https://registry.npmjs.org/@nevermorelove%2fompp",
|
|
31
|
+
{ timeout: 5000 },
|
|
32
|
+
(res) => {
|
|
33
|
+
if (res.statusCode !== 200) {
|
|
34
|
+
res.resume();
|
|
35
|
+
return resolve(null);
|
|
36
|
+
}
|
|
37
|
+
let body = "";
|
|
38
|
+
res.on("data", (c) => (body += c));
|
|
39
|
+
res.on("end", () => {
|
|
40
|
+
try {
|
|
41
|
+
resolve(JSON.parse(body)["dist-tags"]?.latest ?? null);
|
|
42
|
+
} catch {
|
|
43
|
+
resolve(null);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
req.on("timeout", () => {
|
|
49
|
+
req.destroy();
|
|
50
|
+
resolve(null);
|
|
51
|
+
});
|
|
52
|
+
req.on("error", () => resolve(null));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isNewerVersion(current, latest) {
|
|
57
|
+
const parse = (v) =>
|
|
58
|
+
v.split(/[.-]/).slice(0, 3).map((n) => parseInt(n, 10) || 0);
|
|
59
|
+
const [cmaj, cmin, cpat] = parse(current);
|
|
60
|
+
const [lmaj, lmin, lpat] = parse(latest);
|
|
61
|
+
if (lmaj !== cmaj) return lmaj > cmaj;
|
|
62
|
+
if (lmin !== cmin) return lmin > cmin;
|
|
63
|
+
return lpat > cpat;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function printUpdateBanner(current, latest) {
|
|
67
|
+
const line = `Update available! ${current} → ${latest}`;
|
|
68
|
+
const hint = `Run "npm i -g @nevermorelove/ompp" to update`;
|
|
69
|
+
const width = Math.max(line.length, hint.length) + 4;
|
|
70
|
+
const pad = (s) => " " + s + " ".repeat(width - 2 - s.length) + " ";
|
|
71
|
+
console.error("┌" + "─".repeat(width) + "┐");
|
|
72
|
+
console.error("│" + " ".repeat(width) + "│");
|
|
73
|
+
console.error("│" + pad(line) + "│");
|
|
74
|
+
console.error("│" + pad(hint) + "│");
|
|
75
|
+
console.error("│" + " ".repeat(width) + "│");
|
|
76
|
+
console.error("└" + "─".repeat(width) + "┘");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function checkForUpdate() {
|
|
80
|
+
if (process.env.OMPP_NO_UPDATE_CHECK === "1") return;
|
|
81
|
+
if (process.env.CI) return;
|
|
82
|
+
// Skip dev checkouts: a repo clone updates via git, not npm.
|
|
83
|
+
if (fs.existsSync(path.join(__dirname, ".git"))) return;
|
|
84
|
+
|
|
85
|
+
const current = require("./package.json").version;
|
|
86
|
+
const latest = await fetchLatestVersion();
|
|
87
|
+
if (latest && isNewerVersion(current, latest)) {
|
|
88
|
+
printUpdateBanner(current, latest);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
24
92
|
|
|
25
|
-
// Modes come from
|
|
26
|
-
// 1. OMPP_MODES_DIR (explicit override)
|
|
27
|
-
// 2. modes
|
|
28
|
-
//
|
|
29
|
-
//
|
|
93
|
+
// Modes come from two places, in priority order:
|
|
94
|
+
// 1. OMPP_MODES_DIR (explicit override; repo checkout, custom folder)
|
|
95
|
+
// 2. ~/.omp/ompp/modes (the user-level home; `ompp create` writes here)
|
|
96
|
+
// Same-named modes from OMPP_MODES_DIR win. The folder next to the script
|
|
97
|
+
// is never scanned: with a global install it sits inside node_modules, and
|
|
98
|
+
// user modes must not live there.
|
|
30
99
|
const DEFAULT_MODES_DIR = path.join(os.homedir(), ".omp", "ompp", "modes");
|
|
31
|
-
const BUNDLED_MODES_DIR = path.join(__dirname, "modes");
|
|
32
100
|
const SOURCES = [];
|
|
33
101
|
if (process.env.OMPP_MODES_DIR) SOURCES.push(process.env.OMPP_MODES_DIR);
|
|
34
|
-
SOURCES.push(BUNDLED_MODES_DIR);
|
|
35
102
|
SOURCES.push(DEFAULT_MODES_DIR);
|
|
36
103
|
const RESERVED_NAMES = ["create", "list", "help", "version"];
|
|
37
104
|
const CONFIG_NAMES = ["config.yml", "config.yaml"];
|
|
@@ -278,16 +345,43 @@ function createMode(name) {
|
|
|
278
345
|
return 0;
|
|
279
346
|
}
|
|
280
347
|
|
|
348
|
+
const CREATE_OPTION = "__create__";
|
|
349
|
+
|
|
350
|
+
function isValidModeName(name) {
|
|
351
|
+
return /^[a-z0-9][a-z0-9-]*$/.test(name);
|
|
352
|
+
}
|
|
353
|
+
|
|
281
354
|
async function pickMode(modeNames) {
|
|
282
|
-
const { select, isCancel, cancel } = require("@clack/prompts");
|
|
355
|
+
const { select, text, isCancel, cancel } = require("@clack/prompts");
|
|
356
|
+
const options = [
|
|
357
|
+
...modeNames.map((name) => ({ value: name, label: name })),
|
|
358
|
+
{ value: CREATE_OPTION, label: "Create a new mode +" },
|
|
359
|
+
];
|
|
283
360
|
const chosen = await select({
|
|
284
361
|
message: "Pick a mode",
|
|
285
|
-
options
|
|
362
|
+
options,
|
|
286
363
|
});
|
|
287
364
|
if (isCancel(chosen)) {
|
|
288
365
|
cancel("Cancelled");
|
|
289
366
|
process.exit(130);
|
|
290
367
|
}
|
|
368
|
+
if (chosen === CREATE_OPTION) {
|
|
369
|
+
const name = await text({
|
|
370
|
+
message: "New mode name",
|
|
371
|
+
validate: (v) => {
|
|
372
|
+
if (!v || !isValidModeName(v)) {
|
|
373
|
+
return "Lowercase letters, digits, and dashes only";
|
|
374
|
+
}
|
|
375
|
+
if (RESERVED_NAMES.includes(v)) return `"${v}" is reserved`;
|
|
376
|
+
return undefined;
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
if (isCancel(name)) {
|
|
380
|
+
cancel("Cancelled");
|
|
381
|
+
process.exit(130);
|
|
382
|
+
}
|
|
383
|
+
return { create: name };
|
|
384
|
+
}
|
|
291
385
|
return chosen;
|
|
292
386
|
}
|
|
293
387
|
|
|
@@ -315,7 +409,39 @@ function pickModePiped(modeNames) {
|
|
|
315
409
|
|
|
316
410
|
|
|
317
411
|
|
|
412
|
+
// Spawn omp in the given mode.
|
|
413
|
+
function launchMode(mode, userArgs, dryRun) {
|
|
414
|
+
const modeDir = modeDirOf(mode.name, mode.source);
|
|
415
|
+
const args = buildArgv(modeDir, userArgs);
|
|
416
|
+
const { bin, shell } = resolveBin();
|
|
417
|
+
|
|
418
|
+
if (dryRun) {
|
|
419
|
+
console.error(`[ompp] mode: ${mode.name}`);
|
|
420
|
+
console.error(`[ompp] omp: ${bin}${shell ? " (shell)" : ""}`);
|
|
421
|
+
console.error(`[ompp] argv: ${JSON.stringify(args)}`);
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.error(`[ompp] mode: ${mode.name}`);
|
|
426
|
+
const child = shell
|
|
427
|
+
? spawn([winQuote(bin), ...args.map(winQuote)].join(" "), {
|
|
428
|
+
stdio: "inherit",
|
|
429
|
+
shell: true,
|
|
430
|
+
})
|
|
431
|
+
: spawn(bin, args, { stdio: "inherit" });
|
|
432
|
+
child.on("error", (err) => {
|
|
433
|
+
console.error(`[ompp] failed to start omp: ${err.message}`);
|
|
434
|
+
process.exitCode = 1;
|
|
435
|
+
});
|
|
436
|
+
child.on("exit", (code, signal) => {
|
|
437
|
+
process.exitCode = code ?? (signal ? 1 : 0);
|
|
438
|
+
});
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
318
442
|
async function main() {
|
|
443
|
+
await checkForUpdate();
|
|
444
|
+
|
|
319
445
|
const argv = process.argv.slice(2);
|
|
320
446
|
|
|
321
447
|
if (argv[0] === "create") {
|
|
@@ -358,13 +484,29 @@ async function main() {
|
|
|
358
484
|
`[ompp] skipped folders without recognized files: ${skipped.join(", ")}`,
|
|
359
485
|
);
|
|
360
486
|
}
|
|
361
|
-
if (!modes.length) {
|
|
487
|
+
if (!modes.length && !process.stdin.isTTY) {
|
|
362
488
|
console.error(
|
|
363
489
|
`You have no modes yet. Create one with "ompp create <name>".`,
|
|
364
490
|
);
|
|
365
491
|
for (const s of SOURCES) console.error(`[ompp] looked in: ${s}`);
|
|
366
492
|
return 1;
|
|
367
493
|
}
|
|
494
|
+
if (!modes.length) {
|
|
495
|
+
// TTY with zero modes: jump straight into creating the first one.
|
|
496
|
+
try {
|
|
497
|
+
const picked = await pickMode([]);
|
|
498
|
+
if (picked && typeof picked === "object" && picked.create) {
|
|
499
|
+
const created = createMode(picked.create);
|
|
500
|
+
if (created !== 0) return created;
|
|
501
|
+
const mode2 = { name: picked.create, source: DEFAULT_MODES_DIR };
|
|
502
|
+
return launchMode(mode2, userArgs, dryRun);
|
|
503
|
+
}
|
|
504
|
+
return 1;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
console.error(`[ompp] ${err.message}`);
|
|
507
|
+
return 1;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
368
510
|
|
|
369
511
|
const modeNames = modes.map((m) => m.name);
|
|
370
512
|
let mode = null;
|
|
@@ -380,44 +522,28 @@ async function main() {
|
|
|
380
522
|
console.error(`[ompp] available: ${modeNames.join(", ")}`);
|
|
381
523
|
return 1;
|
|
382
524
|
}
|
|
383
|
-
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!mode) {
|
|
384
528
|
try {
|
|
385
|
-
const
|
|
529
|
+
const picked = process.stdin.isTTY
|
|
386
530
|
? await pickMode(modeNames)
|
|
387
531
|
: await pickModePiped(modeNames);
|
|
388
|
-
|
|
532
|
+
if (picked && typeof picked === "object" && picked.create) {
|
|
533
|
+
const created = createMode(picked.create);
|
|
534
|
+
if (created !== 0) return created;
|
|
535
|
+
// Launch the freshly created mode right away.
|
|
536
|
+
mode = { name: picked.create, source: DEFAULT_MODES_DIR };
|
|
537
|
+
} else {
|
|
538
|
+
mode = modes.find((m) => m.name === picked);
|
|
539
|
+
}
|
|
389
540
|
} catch (err) {
|
|
390
541
|
console.error(`[ompp] ${err.message}`);
|
|
391
542
|
return 1;
|
|
392
543
|
}
|
|
393
544
|
}
|
|
394
545
|
|
|
395
|
-
|
|
396
|
-
const args = buildArgv(modeDir, userArgs);
|
|
397
|
-
const { bin, shell } = resolveBin();
|
|
398
|
-
|
|
399
|
-
if (dryRun) {
|
|
400
|
-
console.error(`[ompp] mode: ${mode.name}`);
|
|
401
|
-
console.error(`[ompp] omp: ${bin}${shell ? " (shell)" : ""}`);
|
|
402
|
-
console.error(`[ompp] argv: ${JSON.stringify(args)}`);
|
|
403
|
-
return 0;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
console.error(`[ompp] mode: ${mode.name}`);
|
|
407
|
-
const child = shell
|
|
408
|
-
? spawn([winQuote(bin), ...args.map(winQuote)].join(" "), {
|
|
409
|
-
stdio: "inherit",
|
|
410
|
-
shell: true,
|
|
411
|
-
})
|
|
412
|
-
: spawn(bin, args, { stdio: "inherit" });
|
|
413
|
-
child.on("error", (err) => {
|
|
414
|
-
console.error(`[ompp] failed to start omp: ${err.message}`);
|
|
415
|
-
process.exitCode = 1;
|
|
416
|
-
});
|
|
417
|
-
child.on("exit", (code, signal) => {
|
|
418
|
-
process.exitCode = code ?? (signal ? 1 : 0);
|
|
419
|
-
});
|
|
420
|
-
return 0;
|
|
546
|
+
return launchMode(mode, userArgs, dryRun);
|
|
421
547
|
}
|
|
422
548
|
|
|
423
549
|
main()
|