@nevermorelove/ompp 0.2.3 → 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/ompp.js +160 -33
- package/package.json +1 -1
package/ompp.js
CHANGED
|
@@ -19,8 +19,76 @@
|
|
|
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
93
|
// Modes come from two places, in priority order:
|
|
26
94
|
// 1. OMPP_MODES_DIR (explicit override; repo checkout, custom folder)
|
|
@@ -277,16 +345,43 @@ function createMode(name) {
|
|
|
277
345
|
return 0;
|
|
278
346
|
}
|
|
279
347
|
|
|
348
|
+
const CREATE_OPTION = "__create__";
|
|
349
|
+
|
|
350
|
+
function isValidModeName(name) {
|
|
351
|
+
return /^[a-z0-9][a-z0-9-]*$/.test(name);
|
|
352
|
+
}
|
|
353
|
+
|
|
280
354
|
async function pickMode(modeNames) {
|
|
281
|
-
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
|
+
];
|
|
282
360
|
const chosen = await select({
|
|
283
361
|
message: "Pick a mode",
|
|
284
|
-
options
|
|
362
|
+
options,
|
|
285
363
|
});
|
|
286
364
|
if (isCancel(chosen)) {
|
|
287
365
|
cancel("Cancelled");
|
|
288
366
|
process.exit(130);
|
|
289
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
|
+
}
|
|
290
385
|
return chosen;
|
|
291
386
|
}
|
|
292
387
|
|
|
@@ -314,7 +409,39 @@ function pickModePiped(modeNames) {
|
|
|
314
409
|
|
|
315
410
|
|
|
316
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
|
+
|
|
317
442
|
async function main() {
|
|
443
|
+
await checkForUpdate();
|
|
444
|
+
|
|
318
445
|
const argv = process.argv.slice(2);
|
|
319
446
|
|
|
320
447
|
if (argv[0] === "create") {
|
|
@@ -357,13 +484,29 @@ async function main() {
|
|
|
357
484
|
`[ompp] skipped folders without recognized files: ${skipped.join(", ")}`,
|
|
358
485
|
);
|
|
359
486
|
}
|
|
360
|
-
if (!modes.length) {
|
|
487
|
+
if (!modes.length && !process.stdin.isTTY) {
|
|
361
488
|
console.error(
|
|
362
489
|
`You have no modes yet. Create one with "ompp create <name>".`,
|
|
363
490
|
);
|
|
364
491
|
for (const s of SOURCES) console.error(`[ompp] looked in: ${s}`);
|
|
365
492
|
return 1;
|
|
366
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
|
+
}
|
|
367
510
|
|
|
368
511
|
const modeNames = modes.map((m) => m.name);
|
|
369
512
|
let mode = null;
|
|
@@ -379,44 +522,28 @@ async function main() {
|
|
|
379
522
|
console.error(`[ompp] available: ${modeNames.join(", ")}`);
|
|
380
523
|
return 1;
|
|
381
524
|
}
|
|
382
|
-
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!mode) {
|
|
383
528
|
try {
|
|
384
|
-
const
|
|
529
|
+
const picked = process.stdin.isTTY
|
|
385
530
|
? await pickMode(modeNames)
|
|
386
531
|
: await pickModePiped(modeNames);
|
|
387
|
-
|
|
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
|
+
}
|
|
388
540
|
} catch (err) {
|
|
389
541
|
console.error(`[ompp] ${err.message}`);
|
|
390
542
|
return 1;
|
|
391
543
|
}
|
|
392
544
|
}
|
|
393
545
|
|
|
394
|
-
|
|
395
|
-
const args = buildArgv(modeDir, userArgs);
|
|
396
|
-
const { bin, shell } = resolveBin();
|
|
397
|
-
|
|
398
|
-
if (dryRun) {
|
|
399
|
-
console.error(`[ompp] mode: ${mode.name}`);
|
|
400
|
-
console.error(`[ompp] omp: ${bin}${shell ? " (shell)" : ""}`);
|
|
401
|
-
console.error(`[ompp] argv: ${JSON.stringify(args)}`);
|
|
402
|
-
return 0;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
console.error(`[ompp] mode: ${mode.name}`);
|
|
406
|
-
const child = shell
|
|
407
|
-
? spawn([winQuote(bin), ...args.map(winQuote)].join(" "), {
|
|
408
|
-
stdio: "inherit",
|
|
409
|
-
shell: true,
|
|
410
|
-
})
|
|
411
|
-
: spawn(bin, args, { stdio: "inherit" });
|
|
412
|
-
child.on("error", (err) => {
|
|
413
|
-
console.error(`[ompp] failed to start omp: ${err.message}`);
|
|
414
|
-
process.exitCode = 1;
|
|
415
|
-
});
|
|
416
|
-
child.on("exit", (code, signal) => {
|
|
417
|
-
process.exitCode = code ?? (signal ? 1 : 0);
|
|
418
|
-
});
|
|
419
|
-
return 0;
|
|
546
|
+
return launchMode(mode, userArgs, dryRun);
|
|
420
547
|
}
|
|
421
548
|
|
|
422
549
|
main()
|