ymmv-cli 0.11.1 → 0.11.2
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 +2 -2
- package/dist/cli.js +103 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,8 +58,8 @@ via npm Trusted Publishing, with provenance.
|
|
|
58
58
|
the commands to run by hand)
|
|
59
59
|
- `ymmv version` prints the CLI version (and notes a newer release when one is known)
|
|
60
60
|
|
|
61
|
-
Values are capped at 256 characters and extra labels at 64
|
|
62
|
-
to 32 extras.
|
|
61
|
+
Values are capped at 256 characters and extra labels at 64, and each needs at
|
|
62
|
+
least one visible character; a profile holds up to 32 extras.
|
|
63
63
|
|
|
64
64
|
Every profile is open JSON too: `GET https://ymmv.fyi/api/v1/u/<handle>`. Full contract
|
|
65
65
|
(shape, statuses, caching, CORS):
|
package/dist/cli.js
CHANGED
|
@@ -393,6 +393,12 @@ function isValidHandle(handle) {
|
|
|
393
393
|
return handle.length >= 1 && handle.length <= 39 && HANDLE_RE.test(handle);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
// ../shared/dist/visible.js
|
|
397
|
+
var VISIBLE_RE = /[^\s\p{Default_Ignorable_Code_Point}\p{Cc}]/u;
|
|
398
|
+
function hasVisibleContent(s) {
|
|
399
|
+
return VISIBLE_RE.test(s);
|
|
400
|
+
}
|
|
401
|
+
|
|
396
402
|
// src/render.ts
|
|
397
403
|
var ESC = String.fromCharCode(27);
|
|
398
404
|
var CSI = `${ESC}[`;
|
|
@@ -425,6 +431,9 @@ var BIDI_RE = new RegExp("\\p{Bidi_Control}", "gu");
|
|
|
425
431
|
function sanitizeValue(value) {
|
|
426
432
|
return value.replace(ANSI_RE, "").replace(CTRL_RE, "").replace(BIDI_RE, "");
|
|
427
433
|
}
|
|
434
|
+
function showsVisibleText(value) {
|
|
435
|
+
return hasVisibleContent(sanitizeValue(value));
|
|
436
|
+
}
|
|
428
437
|
function useColor(env, isTTY) {
|
|
429
438
|
if (env.NO_COLOR !== void 0) return false;
|
|
430
439
|
if (env.FORCE_COLOR !== void 0) {
|
|
@@ -597,10 +606,16 @@ function causeText(err) {
|
|
|
597
606
|
return sanitizeValue(text);
|
|
598
607
|
}
|
|
599
608
|
function wireText(text) {
|
|
600
|
-
|
|
601
|
-
|
|
609
|
+
return capped(sanitizeValue(String(text)));
|
|
610
|
+
}
|
|
611
|
+
var WIRE_TEXT_CAP = 200;
|
|
612
|
+
function head(clean) {
|
|
613
|
+
return [...clean.slice(0, WIRE_TEXT_CAP * 2)].slice(0, WIRE_TEXT_CAP).join("");
|
|
614
|
+
}
|
|
615
|
+
function capped(clean) {
|
|
616
|
+
const kept = head(clean);
|
|
617
|
+
return kept.length < clean.length ? `${kept}\u2026` : clean;
|
|
602
618
|
}
|
|
603
|
-
var INVISIBLE_RE = new RegExp("\\p{Default_Ignorable_Code_Point}", "gu");
|
|
604
619
|
async function wireBody(res) {
|
|
605
620
|
try {
|
|
606
621
|
return await res.text();
|
|
@@ -615,8 +630,8 @@ function wireErrorBody(raw) {
|
|
|
615
630
|
const out = {};
|
|
616
631
|
if (typeof body?.error === "string") out.slug = body.error;
|
|
617
632
|
if (typeof body?.message === "string") {
|
|
618
|
-
const clean =
|
|
619
|
-
if (
|
|
633
|
+
const clean = sanitizeValue(body.message);
|
|
634
|
+
if (hasVisibleContent(head(clean))) out.message = capped(clean).trim();
|
|
620
635
|
}
|
|
621
636
|
return out;
|
|
622
637
|
} catch {
|
|
@@ -1712,7 +1727,31 @@ function pagePointer(handle) {
|
|
|
1712
1727
|
const c = palette(color);
|
|
1713
1728
|
return ` ${c.faint}\u2192 ${color ? displayUrl(BASE) : BASE}/${handle}${c.reset}`;
|
|
1714
1729
|
}
|
|
1715
|
-
|
|
1730
|
+
function writeRuleRefusal(entries, existing) {
|
|
1731
|
+
const saved = savedKeys(existing);
|
|
1732
|
+
for (const { key, value } of entries) {
|
|
1733
|
+
if (!isCuratedKey(key)) continue;
|
|
1734
|
+
const problem = valueProblem(value);
|
|
1735
|
+
if (problem === void 0) continue;
|
|
1736
|
+
const remedy = problem === "invisible" ? "Set one" : "Set a shorter one";
|
|
1737
|
+
const source = saved.has(key) ? "saved" : "detected";
|
|
1738
|
+
const remove = problem === "invisible" && source === "saved" ? `, or remove it: ymmv unset ${key}` : "";
|
|
1739
|
+
return `The ${source} ${KEY_LABELS[key]} value ${ruleClause(problem, value)}. ${remedy}: ymmv set ${key} <value>${remove}.`;
|
|
1740
|
+
}
|
|
1741
|
+
return void 0;
|
|
1742
|
+
}
|
|
1743
|
+
function valueProblem(value) {
|
|
1744
|
+
if (!showsVisibleText(value)) return "invisible";
|
|
1745
|
+
if (value.length > MAX_VALUE) return "over-cap";
|
|
1746
|
+
return void 0;
|
|
1747
|
+
}
|
|
1748
|
+
function ruleClause(problem, value) {
|
|
1749
|
+
return problem === "invisible" ? "has no visible text" : `is ${value.length} characters; the cap is ${MAX_VALUE}`;
|
|
1750
|
+
}
|
|
1751
|
+
function savedKeys(existing) {
|
|
1752
|
+
return new Set((existing?.entries ?? []).map((e) => e.key));
|
|
1753
|
+
}
|
|
1754
|
+
async function promptEntries(defaults, saved, prompter) {
|
|
1716
1755
|
const c = palette(colorEnabled());
|
|
1717
1756
|
console.log(message(`${c.faint}Enter to keep, "-" to clear${c.reset}`));
|
|
1718
1757
|
const chosen = /* @__PURE__ */ new Map();
|
|
@@ -1720,13 +1759,15 @@ async function promptEntries(defaults, prompter) {
|
|
|
1720
1759
|
for (; ; ) {
|
|
1721
1760
|
const answer = (await prompter.ask(KEY_LABELS[key], defaults.get(key))).trim();
|
|
1722
1761
|
const value = answer === "-" ? "" : answer;
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
);
|
|
1762
|
+
const rawDefault = defaults.get(key) ?? "";
|
|
1763
|
+
const isDefault = value === sanitizeValue(rawDefault).trim();
|
|
1764
|
+
const emptiedDefault = answer === "" && rawDefault !== "";
|
|
1765
|
+
const problem = emptiedDefault ? "invisible" : value === "" ? void 0 : valueProblem(value);
|
|
1766
|
+
if (problem !== void 0) {
|
|
1767
|
+
const which = `the ${saved.has(key) ? "saved" : "detected"} value`;
|
|
1768
|
+
const clause = ruleClause(problem, value);
|
|
1769
|
+
const note = isDefault ? `${which} ${clause}. Type a ${problem === "invisible" ? "value" : "shorter value"} or - to clear` : `that value ${clause}`;
|
|
1770
|
+
console.log(message(`${c.faint}${note}${c.reset}`));
|
|
1730
1771
|
continue;
|
|
1731
1772
|
}
|
|
1732
1773
|
if (value) chosen.set(key, value);
|
|
@@ -1788,26 +1829,23 @@ async function publish(io) {
|
|
|
1788
1829
|
let values = defaults;
|
|
1789
1830
|
const assemble = () => [...entriesFromMap(values), ...carried];
|
|
1790
1831
|
const edits = /* @__PURE__ */ new Map();
|
|
1832
|
+
let saved = savedKeys(existing);
|
|
1791
1833
|
const prompt = async (prompter) => {
|
|
1792
1834
|
const before = values;
|
|
1793
|
-
values = await promptEntries(values, prompter);
|
|
1835
|
+
values = await promptEntries(values, saved, prompter);
|
|
1794
1836
|
for (const key of CURATED_KEYS) {
|
|
1795
1837
|
const after = values.get(key);
|
|
1796
1838
|
if (after !== before.get(key)) edits.set(key, after);
|
|
1797
1839
|
}
|
|
1798
1840
|
};
|
|
1799
1841
|
if (!io.interactive || !io.prompter || io.yes) {
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
`The detected ${KEY_LABELS[over[0]]} value is ${over[1].length} characters; the cap is ${MAX_VALUE}. Set a shorter one: ymmv set ${over[0]} <value>.`
|
|
1805
|
-
)
|
|
1806
|
-
);
|
|
1842
|
+
const entries = assemble();
|
|
1843
|
+
const refusal = writeRuleRefusal(entries, existing);
|
|
1844
|
+
if (refusal !== void 0) {
|
|
1845
|
+
console.error(message(refusal));
|
|
1807
1846
|
process.exitCode = 1;
|
|
1808
1847
|
return;
|
|
1809
1848
|
}
|
|
1810
|
-
const entries = assemble();
|
|
1811
1849
|
showCard(entries);
|
|
1812
1850
|
printPublished(
|
|
1813
1851
|
await publishProfile(newProfile(handle, entries, extras), cred, { ifMatch }),
|
|
@@ -1817,7 +1855,9 @@ async function publish(io) {
|
|
|
1817
1855
|
}
|
|
1818
1856
|
try {
|
|
1819
1857
|
if (!existing) await prompt(io.prompter);
|
|
1858
|
+
const failsRule = ([, v]) => valueProblem(v) !== void 0;
|
|
1820
1859
|
for (; ; ) {
|
|
1860
|
+
if ([...values].some(failsRule)) await prompt(io.prompter);
|
|
1821
1861
|
const entries = assemble();
|
|
1822
1862
|
showCard(entries);
|
|
1823
1863
|
const ans = await io.prompter.choice(
|
|
@@ -1854,6 +1894,7 @@ async function publish(io) {
|
|
|
1854
1894
|
else rebased.set(key, value);
|
|
1855
1895
|
}
|
|
1856
1896
|
values = rebased;
|
|
1897
|
+
saved = savedKeys(fresh.profile);
|
|
1857
1898
|
carried = unknownEntries(fresh.profile);
|
|
1858
1899
|
extras = fresh.profile.extras;
|
|
1859
1900
|
ifMatch = fresh.etag;
|
|
@@ -1954,18 +1995,24 @@ async function runSet(target) {
|
|
|
1954
1995
|
process.exitCode = 1;
|
|
1955
1996
|
return;
|
|
1956
1997
|
}
|
|
1998
|
+
const refusal = writeRuleRefusal(entries, existing);
|
|
1999
|
+
if (refusal !== void 0) {
|
|
2000
|
+
console.error(message(refusal));
|
|
2001
|
+
process.exitCode = 1;
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
1957
2004
|
const res = await publishProfile(newProfile(handle, entries, extras), cred, {
|
|
1958
2005
|
ifMatch: own?.etag
|
|
1959
2006
|
});
|
|
1960
|
-
const line = target.kind === "curated" ? `Set ${KEY_LABELS[target.key]} = ${target.value}.` : `Set extra ${target.label} = ${target.value}.`;
|
|
2007
|
+
const line = target.kind === "curated" ? `Set ${KEY_LABELS[target.key]} = ${sanitizeValue(target.value)}.` : `Set extra ${sanitizeValue(target.label)} = ${sanitizeValue(target.value)}.`;
|
|
1961
2008
|
console.log(message(`${line}${pagePointer(res.handle)}`));
|
|
1962
2009
|
}
|
|
1963
2010
|
var PASTE_SAFE_LABEL = /^[\p{L}\p{N} _.+/:-]+$/u;
|
|
1964
2011
|
function extraHint(existing, label) {
|
|
1965
2012
|
const eq = label.indexOf("=");
|
|
1966
|
-
const
|
|
1967
|
-
if (!
|
|
1968
|
-
const shown = PASTE_SAFE_LABEL.test(
|
|
2013
|
+
const head2 = eq > 0 ? label.slice(0, eq).trim() : "";
|
|
2014
|
+
if (!head2 || !applyUnset(existing, { kind: "extra", label: head2 }).removed) return "";
|
|
2015
|
+
const shown = PASTE_SAFE_LABEL.test(head2) ? head2 : "Label";
|
|
1969
2016
|
return `
|
|
1970
2017
|
(unset takes just the label: ymmv unset --extra "${shown}")`;
|
|
1971
2018
|
}
|
|
@@ -1986,6 +2033,12 @@ async function runUnset(target) {
|
|
|
1986
2033
|
console.log(message(line2));
|
|
1987
2034
|
return;
|
|
1988
2035
|
}
|
|
2036
|
+
const refusal = writeRuleRefusal(entries, existing);
|
|
2037
|
+
if (refusal !== void 0) {
|
|
2038
|
+
console.error(message(refusal));
|
|
2039
|
+
process.exitCode = 1;
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
1989
2042
|
const res = await publishProfile(newProfile(handle, entries, extras), cred, {
|
|
1990
2043
|
ifMatch: own?.etag
|
|
1991
2044
|
});
|
|
@@ -2034,10 +2087,10 @@ var SET_USAGE = `usage: ymmv set <key> <value> | ${SET_EXTRA}`;
|
|
|
2034
2087
|
var EXTRA_USAGE = `usage: ${SET_EXTRA}`;
|
|
2035
2088
|
var UNSET_USAGE = `usage: ymmv unset <key> | ${UNSET_EXTRA}`;
|
|
2036
2089
|
var VIEW_USAGE = "usage: ymmv view <handle>";
|
|
2037
|
-
function invalidKeyError(
|
|
2090
|
+
function invalidKeyError(head2, hint) {
|
|
2038
2091
|
return {
|
|
2039
2092
|
kind: "error",
|
|
2040
|
-
message: `"${sanitizeValue(
|
|
2093
|
+
message: `"${sanitizeValue(head2)}" is not a curated key. Valid keys: ${CURATED_KEYS.join(", ")}.
|
|
2041
2094
|
For anything else, use: ${hint}.`
|
|
2042
2095
|
};
|
|
2043
2096
|
}
|
|
@@ -2061,9 +2114,15 @@ function valueCapError(value) {
|
|
|
2061
2114
|
message: `That value is ${value.length} characters; the cap is ${MAX_VALUE}.`
|
|
2062
2115
|
};
|
|
2063
2116
|
}
|
|
2117
|
+
function labelInvisibleError() {
|
|
2118
|
+
return { kind: "error", message: "That label has no visible text." };
|
|
2119
|
+
}
|
|
2120
|
+
function valueInvisibleError() {
|
|
2121
|
+
return { kind: "error", message: "That value has no visible text." };
|
|
2122
|
+
}
|
|
2064
2123
|
function parseSet(rest) {
|
|
2065
|
-
const
|
|
2066
|
-
if (
|
|
2124
|
+
const head2 = rest[0];
|
|
2125
|
+
if (head2 === "--extra" || head2 === "-e") {
|
|
2067
2126
|
const spec = rest.slice(1).join(" ").trim();
|
|
2068
2127
|
const eq = spec.indexOf("=");
|
|
2069
2128
|
if (eq <= 0) return { kind: "error", message: EXTRA_USAGE };
|
|
@@ -2071,30 +2130,33 @@ function parseSet(rest) {
|
|
|
2071
2130
|
const value2 = spec.slice(eq + 1).trim();
|
|
2072
2131
|
if (!label || !value2) return { kind: "error", message: EXTRA_USAGE };
|
|
2073
2132
|
if (value2 === "-") return { kind: "unset", target: { kind: "extra", label } };
|
|
2133
|
+
if (!showsVisibleText(label)) return labelInvisibleError();
|
|
2134
|
+
if (!showsVisibleText(value2)) return valueInvisibleError();
|
|
2074
2135
|
if (label.length > MAX_LABEL) return labelCapError(label);
|
|
2075
2136
|
if (value2.length > MAX_VALUE) return valueCapError(value2);
|
|
2076
2137
|
return { kind: "set", target: { kind: "extra", label, value: value2 } };
|
|
2077
2138
|
}
|
|
2078
|
-
if (!
|
|
2079
|
-
if (!isCuratedKey(
|
|
2139
|
+
if (!head2) return { kind: "error", message: SET_USAGE };
|
|
2140
|
+
if (!isCuratedKey(head2)) return invalidKeyError(head2, SET_EXTRA);
|
|
2080
2141
|
const value = rest.slice(1).join(" ").trim();
|
|
2081
|
-
if (!value) return { kind: "error", message: `usage: ymmv set ${
|
|
2082
|
-
if (value === "-") return { kind: "unset", target: { kind: "curated", key:
|
|
2142
|
+
if (!value) return { kind: "error", message: `usage: ymmv set ${head2} <value>` };
|
|
2143
|
+
if (value === "-") return { kind: "unset", target: { kind: "curated", key: head2 } };
|
|
2144
|
+
if (!showsVisibleText(value)) return valueInvisibleError();
|
|
2083
2145
|
if (value.length > MAX_VALUE) return valueCapError(value);
|
|
2084
|
-
return { kind: "set", target: { kind: "curated", key:
|
|
2146
|
+
return { kind: "set", target: { kind: "curated", key: head2, value } };
|
|
2085
2147
|
}
|
|
2086
2148
|
function parseUnset(rest) {
|
|
2087
|
-
const
|
|
2088
|
-
if (
|
|
2149
|
+
const head2 = rest[0];
|
|
2150
|
+
if (head2 === "--extra" || head2 === "-e") {
|
|
2089
2151
|
const label = rest.slice(1).join(" ").trim();
|
|
2090
2152
|
if (!label) return { kind: "error", message: `usage: ${UNSET_EXTRA}` };
|
|
2091
2153
|
if (label.length > MAX_LABEL) return labelCapError(label);
|
|
2092
2154
|
return { kind: "unset", target: { kind: "extra", label } };
|
|
2093
2155
|
}
|
|
2094
|
-
if (!
|
|
2095
|
-
if (!isCuratedKey(
|
|
2096
|
-
if (rest.length > 1) return { kind: "error", message: `usage: ymmv unset ${
|
|
2097
|
-
return { kind: "unset", target: { kind: "curated", key:
|
|
2156
|
+
if (!head2) return { kind: "error", message: UNSET_USAGE };
|
|
2157
|
+
if (!isCuratedKey(head2)) return invalidKeyError(head2, UNSET_EXTRA);
|
|
2158
|
+
if (rest.length > 1) return { kind: "error", message: `usage: ymmv unset ${head2}` };
|
|
2159
|
+
return { kind: "unset", target: { kind: "curated", key: head2 } };
|
|
2098
2160
|
}
|
|
2099
2161
|
function resolveArg(argv) {
|
|
2100
2162
|
const first = argv[0];
|