ymmv-cli 0.8.0 → 0.9.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 +16 -4
- package/dist/cli.js +309 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,10 +9,13 @@ clean page at `ymmv.fyi/<handle>` in about 10 seconds. See a live one:
|
|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npx ymmv-cli # detect your stack, confirm, go live at ymmv.fyi/<you>
|
|
13
|
-
npx ymmv-cli bardisty # view someone's stack in the terminal
|
|
12
|
+
npx ymmv-cli@latest # detect your stack, confirm, go live at ymmv.fyi/<you>
|
|
13
|
+
npx ymmv-cli@latest bardisty # view someone's stack in the terminal
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
(`@latest` makes npx look for the newest release; without a version spec, npx can pin you
|
|
17
|
+
to whatever it downloaded first.)
|
|
18
|
+
|
|
16
19
|
Viewing someone while you're logged in diffs their stack against yours:
|
|
17
20
|
|
|
18
21
|
```
|
|
@@ -33,6 +36,9 @@ Install once for the short `ymmv` command:
|
|
|
33
36
|
npm i -g ymmv-cli
|
|
34
37
|
```
|
|
35
38
|
|
|
39
|
+
When a newer release exists, the CLI mentions it after a command (interactive terminals only)
|
|
40
|
+
and `ymmv update` upgrades a global install in place.
|
|
41
|
+
|
|
36
42
|
First run includes a one-time GitHub sign-in. Works on macOS, Linux, Windows,
|
|
37
43
|
and WSL. Nothing is published until you confirm (detection only pre-fills), and
|
|
38
44
|
`ymmv delete` removes everything. Releases are published from GitHub Actions
|
|
@@ -47,7 +53,10 @@ via npm Trusted Publishing, with provenance.
|
|
|
47
53
|
- `ymmv unset editor` removes one value (`ymmv set editor -` works too); `ymmv unset --extra "Keyboard"` removes an extra
|
|
48
54
|
- `ymmv delete` removes your profile (`ymmv delete -y` skips the confirm, for scripts)
|
|
49
55
|
- `ymmv login` / `ymmv logout` sign in / out
|
|
50
|
-
- `ymmv
|
|
56
|
+
- `ymmv update` updates the CLI to the latest release (runs the matching upgrade for npm, pnpm,
|
|
57
|
+
and bun global installs; via npx it prints the invocation to use instead; anything else gets
|
|
58
|
+
the commands to run by hand)
|
|
59
|
+
- `ymmv version` prints the CLI version (and notes a newer release when one is known)
|
|
51
60
|
|
|
52
61
|
Values are capped at 256 characters and extra labels at 64; a profile holds up
|
|
53
62
|
to 32 extras.
|
|
@@ -68,6 +77,9 @@ Every profile is open JSON too: `GET https://ymmv.fyi/api/v1/u/<handle>`. Full c
|
|
|
68
77
|
- `YMMV_HANDLE` names the GitHub username `YMMV_TOKEN` belongs to. Required for `ymmv -y` and
|
|
69
78
|
`ymmv set`/`unset` under an env token (there is no server lookup for it); ignored without
|
|
70
79
|
`YMMV_TOKEN`.
|
|
80
|
+
- `YMMV_NO_UPDATE_CHECK` disables the startup check for newer releases (the ecosystem-standard
|
|
81
|
+
`NO_UPDATE_NOTIFIER` works too). The check is also off automatically under `CI`, in pipes,
|
|
82
|
+
and in dev builds; it never blocks or fails a command.
|
|
71
83
|
|
|
72
84
|
## Publishing from CI
|
|
73
85
|
|
|
@@ -78,7 +90,7 @@ Every profile is open JSON too: `GET https://ymmv.fyi/api/v1/u/<handle>`. Full c
|
|
|
78
90
|
`~/.config/ymmv/token.json` (Linux), `~/Library/Preferences/ymmv/token.json` (macOS),
|
|
79
91
|
`%APPDATA%\ymmv\Config\token.json` (Windows).
|
|
80
92
|
3. Set it as a CI secret named `YMMV_TOKEN`, and set `YMMV_HANDLE` to your GitHub username.
|
|
81
|
-
4. Run `npx ymmv-cli -y` in the job.
|
|
93
|
+
4. Run `npx ymmv-cli@latest -y` in the job.
|
|
82
94
|
|
|
83
95
|
Two things to know:
|
|
84
96
|
|
package/dist/cli.js
CHANGED
|
@@ -349,7 +349,8 @@ var CLI_VERBS = [
|
|
|
349
349
|
"view",
|
|
350
350
|
"help",
|
|
351
351
|
"publish",
|
|
352
|
-
"version"
|
|
352
|
+
"version",
|
|
353
|
+
"update"
|
|
353
354
|
];
|
|
354
355
|
var RESERVED = [.../* @__PURE__ */ new Set([...RESERVED_ROUTES, ...CLI_VERBS])];
|
|
355
356
|
var RESERVED_SET = new Set(RESERVED);
|
|
@@ -417,10 +418,10 @@ function isHttpUrl(value) {
|
|
|
417
418
|
return HTTP_URL_RE.test(value.trim());
|
|
418
419
|
}
|
|
419
420
|
var NO_OSC8_TERMS = /* @__PURE__ */ new Set(["linux", "dumb"]);
|
|
420
|
-
function link(url, color, term = process.env.TERM) {
|
|
421
|
+
function link(url, color, term = process.env.TERM, label) {
|
|
421
422
|
const clean = sanitizeValue(url).trim();
|
|
422
423
|
if (!color) return clean;
|
|
423
|
-
const text = `${CODES.amber}${displayUrl(clean)}${CODES.reset}`;
|
|
424
|
+
const text = `${CODES.amber}${label ? sanitizeValue(label) : displayUrl(clean)}${CODES.reset}`;
|
|
424
425
|
if (term !== void 0 && NO_OSC8_TERMS.has(term)) return text;
|
|
425
426
|
return `${OSC}8;;${clean}${ST}${text}${OSC}8;;${ST}`;
|
|
426
427
|
}
|
|
@@ -539,7 +540,7 @@ function nudge(color) {
|
|
|
539
540
|
function notFound(handle, color, base) {
|
|
540
541
|
return `
|
|
541
542
|
no ymmv profile for "${sanitizeValue(handle)}" yet.
|
|
542
|
-
publish one at ${link(base, color)} with: npx ymmv-cli`;
|
|
543
|
+
publish one at ${link(base, color)} with: npx ymmv-cli@latest`;
|
|
543
544
|
}
|
|
544
545
|
|
|
545
546
|
// src/http.ts
|
|
@@ -626,9 +627,6 @@ async function safeFetch(url, init, reach, fetchFn = globalThis.fetch) {
|
|
|
626
627
|
}
|
|
627
628
|
}
|
|
628
629
|
|
|
629
|
-
// src/index.ts
|
|
630
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
631
|
-
|
|
632
630
|
// src/config.ts
|
|
633
631
|
function normalizeBase(raw) {
|
|
634
632
|
return raw.replace(/\/+$/, "");
|
|
@@ -1827,7 +1825,7 @@ function resolveArg(argv) {
|
|
|
1827
1825
|
}
|
|
1828
1826
|
return { kind: "publish", yes: true };
|
|
1829
1827
|
}
|
|
1830
|
-
if (first === "login" || first === "logout") return noArgs(first, rest);
|
|
1828
|
+
if (first === "login" || first === "logout" || first === "update") return noArgs(first, rest);
|
|
1831
1829
|
if (first === "publish") {
|
|
1832
1830
|
return yesOnly("usage: ymmv publish [-y]", rest, (yes) => ({ kind: "publish", yes }));
|
|
1833
1831
|
}
|
|
@@ -1877,6 +1875,274 @@ function reservedError(handle, hintVerbs = false) {
|
|
|
1877
1875
|
};
|
|
1878
1876
|
}
|
|
1879
1877
|
|
|
1878
|
+
// src/update.ts
|
|
1879
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
1880
|
+
import { homedir } from "os";
|
|
1881
|
+
|
|
1882
|
+
// src/update-check.ts
|
|
1883
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1884
|
+
import { readFileSync as readFileSync2, realpathSync } from "fs";
|
|
1885
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
1886
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1887
|
+
import envPaths2 from "env-paths";
|
|
1888
|
+
var REGISTRY_URL = "https://registry.npmjs.org/ymmv-cli/latest";
|
|
1889
|
+
var RELEASES_URL = "https://github.com/ymmv-fyi/ymmv/releases";
|
|
1890
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
1891
|
+
var FINISH_GRACE_MS = 500;
|
|
1892
|
+
var SUCCESS_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1893
|
+
var FAILURE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
1894
|
+
var VERSION_FRESH_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1895
|
+
function ownVersion() {
|
|
1896
|
+
try {
|
|
1897
|
+
const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
|
|
1898
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1899
|
+
} catch {
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function parseTriple(v) {
|
|
1904
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
|
|
1905
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
1906
|
+
}
|
|
1907
|
+
function isNewer(latest, current) {
|
|
1908
|
+
const l = parseTriple(latest);
|
|
1909
|
+
const c = parseTriple(current);
|
|
1910
|
+
if (!l || !c) return false;
|
|
1911
|
+
for (let i = 0; i < 3; i++) {
|
|
1912
|
+
if (l[i] !== c[i]) return l[i] > c[i];
|
|
1913
|
+
}
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
function updateCachePath() {
|
|
1917
|
+
return join2(envPaths2("ymmv", { suffix: "" }).config, "update-check.json");
|
|
1918
|
+
}
|
|
1919
|
+
async function readCache(path, now) {
|
|
1920
|
+
try {
|
|
1921
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
1922
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1923
|
+
const c = parsed;
|
|
1924
|
+
if (typeof c.lastCheckedAt !== "number" || typeof c.ok !== "boolean") return null;
|
|
1925
|
+
if (!Number.isFinite(c.lastCheckedAt) || c.lastCheckedAt > now) return null;
|
|
1926
|
+
if (c.latest !== void 0 && typeof c.latest !== "string") return null;
|
|
1927
|
+
if (c.latestAt !== void 0 && (!Number.isFinite(c.latestAt) || c.latestAt > now)) return null;
|
|
1928
|
+
return { lastCheckedAt: c.lastCheckedAt, ok: c.ok, latest: c.latest, latestAt: c.latestAt };
|
|
1929
|
+
} catch {
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
async function writeCache(path, cache) {
|
|
1934
|
+
const tmp = `${path}.${randomUUID2()}.tmp`;
|
|
1935
|
+
try {
|
|
1936
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
1937
|
+
await writeFile2(tmp, JSON.stringify(cache));
|
|
1938
|
+
await rename2(tmp, path);
|
|
1939
|
+
} catch {
|
|
1940
|
+
await rm2(tmp, { force: true }).catch(() => {
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function binRealpath() {
|
|
1945
|
+
const raw = process.argv[1];
|
|
1946
|
+
if (!raw) return void 0;
|
|
1947
|
+
try {
|
|
1948
|
+
return realpathSync(raw);
|
|
1949
|
+
} catch {
|
|
1950
|
+
return raw;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
var NPM_PREFIX_ROOTS = /* @__PURE__ */ new Set(["usr", "local", "homebrew", "nodejs", "node", "installation"]);
|
|
1954
|
+
var VERSIONISH_RE = /^v?\d+(\.\d+)*$/;
|
|
1955
|
+
function detectInstallMethod(binPath) {
|
|
1956
|
+
if (!binPath) return "unknown";
|
|
1957
|
+
const segs = binPath.split(/[\\/]+/).filter(Boolean).map((s) => s.toLowerCase());
|
|
1958
|
+
const has = (name) => segs.includes(name);
|
|
1959
|
+
if (has("_npx")) return "ephemeral";
|
|
1960
|
+
if (has("dlx") || segs.some((s) => s.startsWith("dlx-"))) return "ephemeral";
|
|
1961
|
+
if (segs.some((s) => s.startsWith("bunx-"))) return "ephemeral";
|
|
1962
|
+
if (has(".bun") && has("cache")) return "ephemeral";
|
|
1963
|
+
if (has(".bun") && (has("global") || has("bin"))) return "bun-global";
|
|
1964
|
+
if (has(".volta")) return "unknown";
|
|
1965
|
+
if (segs.some((s, i) => s === "pnpm" && segs[i + 1] === "global")) return "pnpm-global";
|
|
1966
|
+
if (has(".pnpm")) return "unknown";
|
|
1967
|
+
const npmGlobal = segs.some(
|
|
1968
|
+
(s, i) => s === "node_modules" && segs[i + 1] === "ymmv-cli" && (segs[i - 1] === "lib" && i >= 2 && (NPM_PREFIX_ROOTS.has(segs[i - 2]) || VERSIONISH_RE.test(segs[i - 2])) || segs[i - 1] === "npm")
|
|
1969
|
+
);
|
|
1970
|
+
if (npmGlobal) return "npm-global";
|
|
1971
|
+
return "unknown";
|
|
1972
|
+
}
|
|
1973
|
+
function npxInvocation(latest) {
|
|
1974
|
+
const triple = latest ? parseTriple(latest) : null;
|
|
1975
|
+
return `npx ymmv-cli@${triple ? triple.join(".") : "latest"}`;
|
|
1976
|
+
}
|
|
1977
|
+
function formatUpdateNotice(current, latest, method, color, term) {
|
|
1978
|
+
const cur = parseTriple(current);
|
|
1979
|
+
const lat = parseTriple(latest);
|
|
1980
|
+
if (!cur || !lat || !isNewer(lat.join("."), cur.join("."))) return null;
|
|
1981
|
+
const latestClean = lat.join(".");
|
|
1982
|
+
const currentClean = cur.join(".");
|
|
1983
|
+
const cmd = method === "ephemeral" ? npxInvocation(latestClean) : "ymmv update";
|
|
1984
|
+
const c = palette(color);
|
|
1985
|
+
const headline = `ymmv-cli ${latestClean} is out`;
|
|
1986
|
+
const linked = color ? link(RELEASES_URL, true, term, headline) : headline;
|
|
1987
|
+
return `${linked} ${c.faint}(you have ${currentClean})${c.reset} \u2192 run ${c.bold}${cmd}${c.reset}`;
|
|
1988
|
+
}
|
|
1989
|
+
function updatesOptedOut(env) {
|
|
1990
|
+
if (env.YMMV_NO_UPDATE_CHECK) return true;
|
|
1991
|
+
if (env.NO_UPDATE_NOTIFIER) return true;
|
|
1992
|
+
if (env.CI && env.CI !== "false") return true;
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
async function readCachedLatest(deps = {}) {
|
|
1996
|
+
const now = (deps.now ?? Date.now)();
|
|
1997
|
+
const cache = await readCache(deps.cachePath ?? updateCachePath(), now);
|
|
1998
|
+
if (!cache?.latest || cache.latestAt === void 0) return null;
|
|
1999
|
+
if (now - cache.latestAt > VERSION_FRESH_MS) return null;
|
|
2000
|
+
const triple = parseTriple(cache.latest);
|
|
2001
|
+
return triple ? triple.join(".") : null;
|
|
2002
|
+
}
|
|
2003
|
+
var GRACE = /* @__PURE__ */ Symbol("grace");
|
|
2004
|
+
function startUpdateCheck(deps = {}) {
|
|
2005
|
+
const env = deps.env ?? process.env;
|
|
2006
|
+
const nowFn = deps.now ?? Date.now;
|
|
2007
|
+
const stderrIsTTY = deps.stderrIsTTY ?? Boolean(process.stderr.isTTY);
|
|
2008
|
+
const inert = { finish: async () => null, abort: () => {
|
|
2009
|
+
} };
|
|
2010
|
+
if (updatesOptedOut(env)) return inert;
|
|
2011
|
+
if (!stderrIsTTY) return inert;
|
|
2012
|
+
const current = deps.currentVersion !== void 0 ? deps.currentVersion : ownVersion();
|
|
2013
|
+
const currentTriple = current === null ? null : parseTriple(current);
|
|
2014
|
+
if (!currentTriple || currentTriple.join(".") === "0.0.0") return inert;
|
|
2015
|
+
const currentClean = currentTriple.join(".");
|
|
2016
|
+
const cachePath = deps.cachePath ?? updateCachePath();
|
|
2017
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
2018
|
+
const method = detectInstallMethod(deps.binPath ?? binRealpath());
|
|
2019
|
+
const color = useColor(env, stderrIsTTY);
|
|
2020
|
+
const term = env.TERM;
|
|
2021
|
+
const controller = new AbortController();
|
|
2022
|
+
const notice = (latest) => latest ? formatUpdateNotice(currentClean, latest, method, color, term) : null;
|
|
2023
|
+
const check = (async () => {
|
|
2024
|
+
const cache = await readCache(cachePath, nowFn());
|
|
2025
|
+
if (cache && nowFn() - cache.lastCheckedAt < (cache.ok ? SUCCESS_TTL_MS : FAILURE_TTL_MS)) {
|
|
2026
|
+
return notice(cache.latest);
|
|
2027
|
+
}
|
|
2028
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
2029
|
+
timeout.unref?.();
|
|
2030
|
+
let fetched = null;
|
|
2031
|
+
try {
|
|
2032
|
+
const res = await doFetch(REGISTRY_URL, {
|
|
2033
|
+
signal: controller.signal,
|
|
2034
|
+
headers: { accept: "application/json" }
|
|
2035
|
+
});
|
|
2036
|
+
if (res.ok) {
|
|
2037
|
+
const body = await res.json();
|
|
2038
|
+
if (typeof body.version === "string") fetched = body.version;
|
|
2039
|
+
}
|
|
2040
|
+
} catch {
|
|
2041
|
+
} finally {
|
|
2042
|
+
clearTimeout(timeout);
|
|
2043
|
+
}
|
|
2044
|
+
const triple = fetched === null ? null : parseTriple(fetched);
|
|
2045
|
+
const canonical2 = triple ? triple.join(".") : void 0;
|
|
2046
|
+
const now = nowFn();
|
|
2047
|
+
await writeCache(cachePath, {
|
|
2048
|
+
lastCheckedAt: now,
|
|
2049
|
+
ok: canonical2 !== void 0,
|
|
2050
|
+
latest: canonical2 ?? cache?.latest,
|
|
2051
|
+
latestAt: canonical2 !== void 0 ? now : cache?.latestAt
|
|
2052
|
+
});
|
|
2053
|
+
return notice(canonical2 ?? cache?.latest);
|
|
2054
|
+
})().catch(() => null);
|
|
2055
|
+
return {
|
|
2056
|
+
async finish() {
|
|
2057
|
+
const result = await new Promise((resolve) => {
|
|
2058
|
+
const timer = setTimeout(() => resolve(GRACE), FINISH_GRACE_MS);
|
|
2059
|
+
timer.unref?.();
|
|
2060
|
+
void check.then((v) => {
|
|
2061
|
+
clearTimeout(timer);
|
|
2062
|
+
resolve(v);
|
|
2063
|
+
});
|
|
2064
|
+
});
|
|
2065
|
+
if (result === GRACE) {
|
|
2066
|
+
controller.abort();
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
return result;
|
|
2070
|
+
},
|
|
2071
|
+
abort() {
|
|
2072
|
+
controller.abort();
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/update.ts
|
|
2078
|
+
var UPGRADE_COMMANDS = {
|
|
2079
|
+
"npm-global": "npm i -g ymmv-cli@latest",
|
|
2080
|
+
"pnpm-global": "pnpm add -g ymmv-cli@latest",
|
|
2081
|
+
"bun-global": "bun add -g ymmv-cli@latest"
|
|
2082
|
+
};
|
|
2083
|
+
var MANUAL_LIST = Object.values(UPGRADE_COMMANDS).map((cmd) => ` ${cmd}`).join("\n");
|
|
2084
|
+
var NOT_FOUND_CODES = /* @__PURE__ */ new Set([127, 9009]);
|
|
2085
|
+
function manualFallback(intro) {
|
|
2086
|
+
return `${intro} Update with the command that matches your setup:
|
|
2087
|
+
${MANUAL_LIST}`;
|
|
2088
|
+
}
|
|
2089
|
+
async function runUpdate(deps = {}) {
|
|
2090
|
+
const method = detectInstallMethod(deps.binPath ?? binRealpath());
|
|
2091
|
+
const c = palette(colorEnabled());
|
|
2092
|
+
if (method === "ephemeral") {
|
|
2093
|
+
const cached = await readCachedLatest({ now: deps.now, cachePath: deps.cachePath });
|
|
2094
|
+
const current = deps.currentVersion !== void 0 ? deps.currentVersion : ownVersion();
|
|
2095
|
+
const pin = cached !== null && current !== null && isNewer(cached, current) ? cached : null;
|
|
2096
|
+
console.log(
|
|
2097
|
+
message(
|
|
2098
|
+
`This run came from a package runner cache (npx-style), so there is no install to update.
|
|
2099
|
+
Next time run: ${c.bold}${npxInvocation(pin)}${c.reset}`
|
|
2100
|
+
)
|
|
2101
|
+
);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (method === "unknown") {
|
|
2105
|
+
console.log(message(manualFallback("Couldn't tell how ymmv-cli was installed.")));
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
const cmd = UPGRADE_COMMANDS[method];
|
|
2109
|
+
const doSpawn = deps.spawn ?? nodeSpawn;
|
|
2110
|
+
const failSpawn = () => {
|
|
2111
|
+
console.error(message(manualFallback(`Couldn't run \`${cmd}\`.`)));
|
|
2112
|
+
process.exitCode = 1;
|
|
2113
|
+
};
|
|
2114
|
+
console.log(message(`${c.faint}running${c.reset} ${c.bold}${cmd}${c.reset}`));
|
|
2115
|
+
await new Promise((resolve) => {
|
|
2116
|
+
let child;
|
|
2117
|
+
try {
|
|
2118
|
+
child = doSpawn(cmd, { stdio: "inherit", shell: true, cwd: homedir() });
|
|
2119
|
+
} catch {
|
|
2120
|
+
failSpawn();
|
|
2121
|
+
resolve();
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
let settled = false;
|
|
2125
|
+
const settle = (fn) => {
|
|
2126
|
+
if (settled) return;
|
|
2127
|
+
settled = true;
|
|
2128
|
+
fn();
|
|
2129
|
+
resolve();
|
|
2130
|
+
};
|
|
2131
|
+
child.on("error", () => {
|
|
2132
|
+
settle(failSpawn);
|
|
2133
|
+
});
|
|
2134
|
+
child.on("exit", (code) => {
|
|
2135
|
+
settle(() => {
|
|
2136
|
+
if (code !== null && NOT_FOUND_CODES.has(code)) {
|
|
2137
|
+
failSpawn();
|
|
2138
|
+
} else if (code) {
|
|
2139
|
+
process.exitCode = code;
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
|
|
1880
2146
|
// src/index.ts
|
|
1881
2147
|
var help = (c) => `${c.bold}ymmv${c.reset}: terminal-native developer tool-stack profiles (ymmv.fyi)
|
|
1882
2148
|
|
|
@@ -1891,6 +2157,7 @@ ${c.faint}Usage:${c.reset}
|
|
|
1891
2157
|
ymmv unset --extra "L" remove a free-form extra
|
|
1892
2158
|
ymmv delete [-y] delete your profile (permanent; -y skips the confirm)
|
|
1893
2159
|
ymmv login | logout GitHub device-flow auth
|
|
2160
|
+
ymmv update update ymmv-cli to the latest release
|
|
1894
2161
|
ymmv help | version
|
|
1895
2162
|
|
|
1896
2163
|
${c.faint}Curated keys:${c.reset} editor, os, shell, prompt, terminal, browser, window-manager,
|
|
@@ -1922,12 +2189,21 @@ async function logout() {
|
|
|
1922
2189
|
console.log(message(revoked ? "Logged out." : "Logged out (no active session on this server)."));
|
|
1923
2190
|
}
|
|
1924
2191
|
function printVersion() {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2192
|
+
const version = ownVersion();
|
|
2193
|
+
console.log(version === null ? "ymmv-cli (version unknown)" : `ymmv-cli ${version}`);
|
|
2194
|
+
}
|
|
2195
|
+
async function printLatestHint() {
|
|
2196
|
+
if (!process.stderr.isTTY) return;
|
|
2197
|
+
if (updatesOptedOut(process.env)) return;
|
|
2198
|
+
const current = ownVersion();
|
|
2199
|
+
const currentTriple = current === null ? null : parseTriple(current);
|
|
2200
|
+
if (!currentTriple || currentTriple.join(".") === "0.0.0") return;
|
|
2201
|
+
const latest = await readCachedLatest();
|
|
2202
|
+
if (latest === null || !isNewer(latest, currentTriple.join("."))) return;
|
|
2203
|
+
const c = palette(useColor(process.env, true));
|
|
2204
|
+
console.error(
|
|
2205
|
+
message(`${c.faint}latest: ${latest}${c.reset} \u2192 run ${c.bold}ymmv update${c.reset}`)
|
|
2206
|
+
);
|
|
1931
2207
|
}
|
|
1932
2208
|
async function interactive(run, yes) {
|
|
1933
2209
|
const isTTY = Boolean(process.stdin.isTTY);
|
|
@@ -1938,9 +2214,12 @@ async function interactive(run, yes) {
|
|
|
1938
2214
|
prompter?.close();
|
|
1939
2215
|
}
|
|
1940
2216
|
}
|
|
2217
|
+
function wantsUpdateCheck(kind) {
|
|
2218
|
+
return kind !== "help" && kind !== "version" && kind !== "error" && kind !== "update";
|
|
2219
|
+
}
|
|
1941
2220
|
async function main(argv) {
|
|
1942
2221
|
const cmd = resolveArg(argv);
|
|
1943
|
-
if (cmd.kind !== "logout") {
|
|
2222
|
+
if (cmd.kind !== "logout" && cmd.kind !== "update") {
|
|
1944
2223
|
const problem = baseProblem() ?? credentialEnvProblem();
|
|
1945
2224
|
if (problem) {
|
|
1946
2225
|
console.error(message(problem));
|
|
@@ -1948,6 +2227,17 @@ async function main(argv) {
|
|
|
1948
2227
|
return;
|
|
1949
2228
|
}
|
|
1950
2229
|
}
|
|
2230
|
+
const update = wantsUpdateCheck(cmd.kind) ? startUpdateCheck() : null;
|
|
2231
|
+
try {
|
|
2232
|
+
await dispatch(cmd);
|
|
2233
|
+
} catch (e) {
|
|
2234
|
+
update?.abort();
|
|
2235
|
+
throw e;
|
|
2236
|
+
}
|
|
2237
|
+
const notice = await update?.finish();
|
|
2238
|
+
if (notice) console.error(message(notice));
|
|
2239
|
+
}
|
|
2240
|
+
async function dispatch(cmd) {
|
|
1951
2241
|
switch (cmd.kind) {
|
|
1952
2242
|
case "publish":
|
|
1953
2243
|
await interactive(publish, cmd.yes);
|
|
@@ -1980,11 +2270,15 @@ async function main(argv) {
|
|
|
1980
2270
|
);
|
|
1981
2271
|
}
|
|
1982
2272
|
break;
|
|
2273
|
+
case "update":
|
|
2274
|
+
await runUpdate();
|
|
2275
|
+
break;
|
|
1983
2276
|
case "help":
|
|
1984
2277
|
console.log(help(palette(colorEnabled())));
|
|
1985
2278
|
break;
|
|
1986
2279
|
case "version":
|
|
1987
2280
|
printVersion();
|
|
2281
|
+
await printLatestHint();
|
|
1988
2282
|
break;
|
|
1989
2283
|
case "error":
|
|
1990
2284
|
console.error(message(cmd.message));
|