@hawkeyexl/manni 1.1.1 → 2.0.1
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 +4 -4
- package/dist/{chunk-DOQUHXK3.js → chunk-MV2T7XAW.js} +69 -43
- package/dist/chunk-MV2T7XAW.js.map +1 -0
- package/dist/{chunk-HYPDUKRU.js → chunk-PDNSN5UJ.js} +13462 -12212
- package/dist/chunk-PDNSN5UJ.js.map +1 -0
- package/dist/chunk-QLVNSAHR.js +4492 -0
- package/dist/chunk-QLVNSAHR.js.map +1 -0
- package/dist/cli.js +558 -27
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +2098 -12
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-DOQUHXK3.js.map +0 -1
- package/dist/chunk-HYPDUKRU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,38 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
STDIN_LINES_MARKER,
|
|
3
4
|
buildProgram,
|
|
5
|
+
collect,
|
|
6
|
+
configOption,
|
|
7
|
+
explicitFalse,
|
|
4
8
|
fail,
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
readStdin,
|
|
10
|
+
reportConfig,
|
|
11
|
+
runIfMain,
|
|
12
|
+
splitList
|
|
13
|
+
} from "./chunk-MV2T7XAW.js";
|
|
7
14
|
import {
|
|
15
|
+
CiteError,
|
|
16
|
+
KeyError,
|
|
17
|
+
SEVERITIES,
|
|
18
|
+
SEVERITY_LIST,
|
|
19
|
+
isSeverity,
|
|
20
|
+
meetsSeverity,
|
|
21
|
+
renderCheckGithub,
|
|
22
|
+
renderCheckJson,
|
|
23
|
+
renderCheckPretty,
|
|
24
|
+
renderUpdateJson,
|
|
25
|
+
renderUpdatePretty,
|
|
26
|
+
runAdd,
|
|
27
|
+
runCheck,
|
|
28
|
+
runKeyRotate,
|
|
29
|
+
runKeySet,
|
|
30
|
+
runUpdate,
|
|
31
|
+
shortCommit,
|
|
32
|
+
shortPin,
|
|
33
|
+
shortSrc,
|
|
34
|
+
spellSource
|
|
35
|
+
} from "./chunk-QLVNSAHR.js";
|
|
36
|
+
import {
|
|
37
|
+
COMMON_FORMAT_LIST,
|
|
38
|
+
OMITTED_WHEN_CLEAN,
|
|
39
|
+
REPORT_FORMATS,
|
|
40
|
+
REPORT_FORMAT_LIST,
|
|
41
|
+
STDIN_TOKEN,
|
|
8
42
|
ToolError,
|
|
9
43
|
escapeWorkflowCommandMessage,
|
|
10
44
|
escapeWorkflowCommandProperty,
|
|
11
45
|
findConfigFile,
|
|
46
|
+
isMachineFormat,
|
|
47
|
+
isReportFormat,
|
|
48
|
+
notice,
|
|
12
49
|
package_default,
|
|
13
50
|
palette,
|
|
14
51
|
programName,
|
|
15
52
|
readConfigFile,
|
|
53
|
+
render,
|
|
16
54
|
selectCollections,
|
|
17
|
-
shouldColor
|
|
18
|
-
|
|
55
|
+
shouldColor,
|
|
56
|
+
terminalConfirm
|
|
57
|
+
} from "./chunk-PDNSN5UJ.js";
|
|
19
58
|
|
|
20
59
|
// src/cli.ts
|
|
21
|
-
import { Command as
|
|
60
|
+
import { Command as Command4 } from "commander";
|
|
22
61
|
|
|
23
62
|
// src/a11y/cli.ts
|
|
24
63
|
import { Command } from "commander";
|
|
25
64
|
|
|
26
|
-
// src/shared/severity.ts
|
|
27
|
-
var SEVERITIES = ["notice", "warning", "error"];
|
|
28
|
-
var SEVERITY_LIST = SEVERITIES.join(" | ");
|
|
29
|
-
function isSeverity(value) {
|
|
30
|
-
return SEVERITIES.includes(value);
|
|
31
|
-
}
|
|
32
|
-
function meetsSeverity(severity, min) {
|
|
33
|
-
return SEVERITIES.indexOf(severity) >= SEVERITIES.indexOf(min);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
65
|
// src/a11y/types.ts
|
|
37
66
|
var A11yError = class extends ToolError {
|
|
38
67
|
constructor(message) {
|
|
@@ -350,7 +379,7 @@ var CHECK_DEFAULTS = Object.freeze({
|
|
|
350
379
|
severity: "notice",
|
|
351
380
|
timeout: 3e4
|
|
352
381
|
});
|
|
353
|
-
async function
|
|
382
|
+
async function runCheck2(opts, deps) {
|
|
354
383
|
const { analyzer } = deps;
|
|
355
384
|
try {
|
|
356
385
|
if (opts.urls.length === 0) {
|
|
@@ -716,7 +745,7 @@ var A11Y_FORMAT_LIST = A11Y_FORMATS.join(" | ");
|
|
|
716
745
|
function isA11yFormat(value) {
|
|
717
746
|
return A11Y_FORMATS.includes(value);
|
|
718
747
|
}
|
|
719
|
-
function
|
|
748
|
+
function render2(format, run, opts) {
|
|
720
749
|
switch (format) {
|
|
721
750
|
case "pretty":
|
|
722
751
|
return renderPretty(run, opts);
|
|
@@ -828,10 +857,10 @@ function truncateMiddle(text, room) {
|
|
|
828
857
|
}
|
|
829
858
|
|
|
830
859
|
// src/a11y/cli.ts
|
|
831
|
-
function
|
|
860
|
+
function collect2(value, prev) {
|
|
832
861
|
return prev.concat([value]);
|
|
833
862
|
}
|
|
834
|
-
function
|
|
863
|
+
function splitList2(value) {
|
|
835
864
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
836
865
|
}
|
|
837
866
|
function resolveColor(program, stream) {
|
|
@@ -887,7 +916,7 @@ function buildProgram2() {
|
|
|
887
916
|
).option("-q, --quiet", "in pretty output, hide pages with no violations").option("--progress", "report progress on stderr (default: only on a terminal)").option("--no-progress", "never report progress").option(
|
|
888
917
|
"--collection <name>",
|
|
889
918
|
"configured collection to run over; repeatable",
|
|
890
|
-
|
|
919
|
+
collect2,
|
|
891
920
|
[]
|
|
892
921
|
).option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").action(async (urls, options, command) => {
|
|
893
922
|
let clearProgress = () => void 0;
|
|
@@ -912,20 +941,20 @@ function buildProgram2() {
|
|
|
912
941
|
const parent = command.parent ?? command;
|
|
913
942
|
const progress = resolveProgress(parent, options.progress);
|
|
914
943
|
clearProgress = progress.clear;
|
|
915
|
-
const run = await
|
|
944
|
+
const run = await runCheck2(
|
|
916
945
|
{
|
|
917
946
|
urls: seeds,
|
|
918
947
|
crawl: typed("crawl") ? options.crawl : cfg.crawl ?? CHECK_DEFAULTS.crawl,
|
|
919
948
|
// Typed flag > config key > no cap. There is no default to fall to.
|
|
920
949
|
maxPages: maxPages ?? cfg.maxPages,
|
|
921
|
-
tags: options.tags !== void 0 ?
|
|
950
|
+
tags: options.tags !== void 0 ? splitList2(options.tags) : cfg.tags ?? CHECK_DEFAULTS.tags,
|
|
922
951
|
severity: typed("severity") ? severity : cfg.severity ?? CHECK_DEFAULTS.severity,
|
|
923
952
|
timeout: typed("timeout") ? timeout : cfg.timeout ?? CHECK_DEFAULTS.timeout
|
|
924
953
|
},
|
|
925
954
|
{ analyzer: createPlaywrightAnalyzer(), onProgress: progress.listener }
|
|
926
955
|
);
|
|
927
956
|
const color = resolveColor(parent, process.stdout);
|
|
928
|
-
const text =
|
|
957
|
+
const text = render2(format, run, { color, quiet: Boolean(options.quiet) });
|
|
929
958
|
if (text.length > 0 || format !== "github") {
|
|
930
959
|
process.stdout.write(`${text}
|
|
931
960
|
`);
|
|
@@ -939,13 +968,507 @@ function buildProgram2() {
|
|
|
939
968
|
return program;
|
|
940
969
|
}
|
|
941
970
|
|
|
942
|
-
// src/cli.ts
|
|
971
|
+
// src/cite/cli.ts
|
|
972
|
+
import { Command as Command2, Option } from "commander";
|
|
973
|
+
var JUNIT_CLASSNAME = "manni.cite";
|
|
974
|
+
var UPDATE_FORMATS = ["pretty", "json"];
|
|
975
|
+
function colorFor(command, isTTY, env) {
|
|
976
|
+
const noColor = colorOwner(command).opts().color === false;
|
|
977
|
+
return shouldColor({ noColor, isTTY, env });
|
|
978
|
+
}
|
|
979
|
+
function colorOwner(command) {
|
|
980
|
+
for (let c = command; c !== null; c = c.parent) {
|
|
981
|
+
if (c.options.some((o) => o.long === "--no-color")) return c;
|
|
982
|
+
}
|
|
983
|
+
return command;
|
|
984
|
+
}
|
|
985
|
+
function isUpdateFormat(value) {
|
|
986
|
+
return UPDATE_FORMATS.includes(value);
|
|
987
|
+
}
|
|
988
|
+
function splitPageArgument(arg) {
|
|
989
|
+
const text = arg.startsWith(STDIN_LINES_MARKER) ? `${STDIN_TOKEN}${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
|
|
990
|
+
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text);
|
|
991
|
+
const head = m === null ? text : text.slice(0, m.index);
|
|
992
|
+
if (m === null || head === "") return { page: arg };
|
|
993
|
+
const start = Number(m[1]);
|
|
994
|
+
const end = m[2] === void 0 ? start : Number(m[2]);
|
|
995
|
+
if (end < start) {
|
|
996
|
+
throw new CiteError(`Invalid range "${arg}": end line ${end} is before start line ${start}.`);
|
|
997
|
+
}
|
|
998
|
+
return { page: head, lines: { start, end } };
|
|
999
|
+
}
|
|
1000
|
+
function spellAt(lines, noun = "line") {
|
|
1001
|
+
return lines.start === lines.end ? `${noun} ${String(lines.start)}` : `${noun}s ${String(lines.start)}-${String(lines.end)}`;
|
|
1002
|
+
}
|
|
1003
|
+
function addMessage(result) {
|
|
1004
|
+
const { citation, claimLines } = result;
|
|
1005
|
+
const claim = citation.claim;
|
|
1006
|
+
const name = citation.id ?? (claim === void 0 ? "a bare pin" : "an entry");
|
|
1007
|
+
const src = shortSrc(spellSource(citation.source));
|
|
1008
|
+
const commit = citation.source["commit-sha"];
|
|
1009
|
+
const pin = `${src}, ${shortPin(citation.source.integrity)}, ${commit === void 0 ? "no commit" : shortCommit(commit)}`;
|
|
1010
|
+
const where2 = result.manifest === void 0 ? "frontmatter" : `${result.manifest.file}:${String(result.manifest.line)}`;
|
|
1011
|
+
const head = `${result.file}: added ${name} to ${where2}`;
|
|
1012
|
+
if (result.markerLine !== void 0) {
|
|
1013
|
+
const at = claimLines === void 0 ? "" : `, claim pinned at line ${String(claimLines.start)}`;
|
|
1014
|
+
return `${head}; marker at line ${String(result.markerLine)}${at}`;
|
|
1015
|
+
}
|
|
1016
|
+
if (claim === void 0 || claimLines === void 0) {
|
|
1017
|
+
return `${head} (source ${pin})`;
|
|
1018
|
+
}
|
|
1019
|
+
if (citation.quote === true) {
|
|
1020
|
+
return `${head} (claim ${spellAt(claimLines, "line")}, a block that reproduces ${src})`;
|
|
1021
|
+
}
|
|
1022
|
+
return `${head} (claim at ${spellAt(claimLines)}, ${shortPin(claim.integrity)}; source ${pin})`;
|
|
1023
|
+
}
|
|
943
1024
|
function buildProgram3() {
|
|
1025
|
+
const program = new Command2();
|
|
1026
|
+
program.name("cite").description(
|
|
1027
|
+
"Track citations from doc claims to source lines and check them for drift."
|
|
1028
|
+
).version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
|
|
1029
|
+
program.command("check").description("Check every citation in the given pages against its source").argument(
|
|
1030
|
+
"[paths...]",
|
|
1031
|
+
"files, directories, or globs to check (use - for stdin)"
|
|
1032
|
+
).option(
|
|
1033
|
+
"--collection <name>",
|
|
1034
|
+
"configured collection to run over; repeatable",
|
|
1035
|
+
collect,
|
|
1036
|
+
[]
|
|
1037
|
+
).option("--ext <list>", "comma-separated extensions for directory walks").option("--exclude <glob>", "glob to exclude; repeatable", collect, []).option("--as <format>", "force an input format for every input").option(
|
|
1038
|
+
"-f, --format <format>",
|
|
1039
|
+
`output: ${REPORT_FORMATS.join(" | ")}`,
|
|
1040
|
+
"pretty"
|
|
1041
|
+
).option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").option("-q, --quiet", "in pretty output, hide current citations and clean files").option("--allow-empty", "treat zero matched files as success").option("--no-gitignore", "check files .gitignore covers").addOption(
|
|
1042
|
+
new Option(
|
|
1043
|
+
"--baseline [path]",
|
|
1044
|
+
"compare against a baseline; fail only on findings not in it"
|
|
1045
|
+
)
|
|
1046
|
+
).addOption(
|
|
1047
|
+
new Option(
|
|
1048
|
+
"--write-baseline [path]",
|
|
1049
|
+
"record current findings as the baseline, then exit 0"
|
|
1050
|
+
)
|
|
1051
|
+
).option("--no-baseline", "ignore a baseline configured by `baseline:`").option("--no-check-sources", "page-side rules only; every source status is skipped").option(
|
|
1052
|
+
"--root <dir>",
|
|
1053
|
+
"directory src: paths resolve from (default: cite.root from config, else the git root, else the current directory)"
|
|
1054
|
+
).option("--show-diff", "in pretty output, print the diff and commit subjects for changed").option("--reveal", "in pretty output, print the decrypted path beside an encrypted source").addHelpText(
|
|
1055
|
+
"after",
|
|
1056
|
+
[
|
|
1057
|
+
"",
|
|
1058
|
+
"Examples:",
|
|
1059
|
+
" manni cite check docs/ # walk a directory",
|
|
1060
|
+
" manni cite check --collection guides # one configured collection",
|
|
1061
|
+
' manni cite check "docs/**/*.md" -f github # CI annotations',
|
|
1062
|
+
" manni cite check --show-diff docs/limits.md # see what changed",
|
|
1063
|
+
" manni cite check --baseline # fail only on new findings",
|
|
1064
|
+
" cat page.md | manni cite check - --as markdown"
|
|
1065
|
+
].join("\n")
|
|
1066
|
+
).action(async (paths, options, command) => {
|
|
1067
|
+
try {
|
|
1068
|
+
const format = options.format;
|
|
1069
|
+
if (!isReportFormat(format)) {
|
|
1070
|
+
throw new CiteError(
|
|
1071
|
+
`Unknown --format "${format}". Use ${REPORT_FORMAT_LIST}.`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
const exts = options.ext ? splitList(options.ext) : void 0;
|
|
1075
|
+
const stdinContent = paths.includes(STDIN_TOKEN) ? await readStdin() : void 0;
|
|
1076
|
+
const cwd = process.cwd();
|
|
1077
|
+
const run = await runCheck({
|
|
1078
|
+
inputs: paths,
|
|
1079
|
+
collection: options.collection,
|
|
1080
|
+
exts,
|
|
1081
|
+
exclude: options.exclude,
|
|
1082
|
+
as: options.as,
|
|
1083
|
+
...configOption(options.config),
|
|
1084
|
+
onConfigLoaded: reportConfig(!isMachineFormat(format), cwd),
|
|
1085
|
+
stdinContent,
|
|
1086
|
+
// `undefined` rather than `false` when absent, so config still wins.
|
|
1087
|
+
allowEmpty: options.allowEmpty ? true : void 0,
|
|
1088
|
+
respectGitignore: explicitFalse(options.gitignore),
|
|
1089
|
+
onNotice: notice,
|
|
1090
|
+
baseline: options.baseline,
|
|
1091
|
+
writeBaseline: options.writeBaseline,
|
|
1092
|
+
checkSources: explicitFalse(options.checkSources),
|
|
1093
|
+
// Only so the run can say git is not there to give diffs; the diffs
|
|
1094
|
+
// themselves are the pretty reporter's.
|
|
1095
|
+
showDiff: options.showDiff ? true : void 0,
|
|
1096
|
+
root: options.root
|
|
1097
|
+
});
|
|
1098
|
+
let text;
|
|
1099
|
+
switch (format) {
|
|
1100
|
+
case "pretty":
|
|
1101
|
+
text = renderCheckPretty(run, {
|
|
1102
|
+
color: colorFor(command, process.stdout.isTTY),
|
|
1103
|
+
quiet: Boolean(options.quiet),
|
|
1104
|
+
showDiff: Boolean(options.showDiff),
|
|
1105
|
+
reveal: Boolean(options.reveal)
|
|
1106
|
+
});
|
|
1107
|
+
break;
|
|
1108
|
+
case "json":
|
|
1109
|
+
text = renderCheckJson(run);
|
|
1110
|
+
break;
|
|
1111
|
+
case "github":
|
|
1112
|
+
text = renderCheckGithub(run);
|
|
1113
|
+
break;
|
|
1114
|
+
default:
|
|
1115
|
+
text = render(format, run.results, run.summary, {
|
|
1116
|
+
frame: run.frame,
|
|
1117
|
+
classname: JUNIT_CLASSNAME,
|
|
1118
|
+
onNotice: notice
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
if (text.length > 0 || !OMITTED_WHEN_CLEAN.has(format)) {
|
|
1122
|
+
process.stdout.write(`${text}
|
|
1123
|
+
`);
|
|
1124
|
+
}
|
|
1125
|
+
process.exitCode = run.summary.failed > 0 ? 1 : 0;
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
fail(err);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
program.command("add").description("Mint a citation for a source range and write it to a page").argument(
|
|
1131
|
+
"<page>",
|
|
1132
|
+
"the page to cite from, with the claim's lines: page:L or page:L1-L2 (use - for stdin, with --as)"
|
|
1133
|
+
).argument(
|
|
1134
|
+
"<src>",
|
|
1135
|
+
"path, path:L or path:L1-L2 relative to --root, or an encrypted ~source with the same line forms"
|
|
1136
|
+
).option("--id <id>", "kebab-case id, unique on the page; required with --marker").option("--marker", "write a cite <id> marker above the page lines and pin the text it anchors").option("--quote", "the page lines are a fenced block that reproduces the source").option(
|
|
1137
|
+
"--encrypt",
|
|
1138
|
+
"write source.file encrypted, with an hmac-sha256- pin; with no key, offer to create one (an available key encrypts without the flag)"
|
|
1139
|
+
).option("--no-commit-sha", "do not record HEAD").option("--dry-run", "print the diff; write nothing").option("--as <format>", "force the page format").option("--root <dir>", "directory source paths resolve from (as check)").option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").addHelpText(
|
|
1140
|
+
"after",
|
|
1141
|
+
[
|
|
1142
|
+
"",
|
|
1143
|
+
"Examples:",
|
|
1144
|
+
" manni cite add docs/limits.md:9 lib/limits.ts:2 --id fetch-timeout",
|
|
1145
|
+
" manni cite add docs/limits.md:30 lib/limits.ts:8-12 --id timeouts --marker",
|
|
1146
|
+
" manni cite add docs/limits.md:14-18 lib/limits.ts:1-3 --quote",
|
|
1147
|
+
" manni cite add docs/limits.md lib/limits.ts --dry-run",
|
|
1148
|
+
" cat page.md | manni cite add -:9 lib/limits.ts:2 --as markdown > out.md"
|
|
1149
|
+
].join("\n")
|
|
1150
|
+
).action(async (pageArgument, src, options) => {
|
|
1151
|
+
try {
|
|
1152
|
+
const { page, lines } = splitPageArgument(pageArgument);
|
|
1153
|
+
const usingStdin = page === STDIN_TOKEN;
|
|
1154
|
+
const stdinContent = usingStdin ? await readStdin() : void 0;
|
|
1155
|
+
const dryRun = Boolean(options.dryRun);
|
|
1156
|
+
const cwd = process.cwd();
|
|
1157
|
+
const result = await runAdd({
|
|
1158
|
+
page,
|
|
1159
|
+
pageLines: lines,
|
|
1160
|
+
src,
|
|
1161
|
+
id: options.id,
|
|
1162
|
+
marker: options.marker ? true : void 0,
|
|
1163
|
+
quote: options.quote ? true : void 0,
|
|
1164
|
+
// `undefined` when absent, so the key decides: an available one
|
|
1165
|
+
// encrypts without the flag.
|
|
1166
|
+
encrypt: options.encrypt ? true : void 0,
|
|
1167
|
+
commitSha: explicitFalse(options.commitSha),
|
|
1168
|
+
dryRun,
|
|
1169
|
+
as: options.as,
|
|
1170
|
+
...configOption(options.config),
|
|
1171
|
+
// With `-` the page owns stdout; so does the diff under --dry-run.
|
|
1172
|
+
onConfigLoaded: reportConfig(!usingStdin && !dryRun, cwd),
|
|
1173
|
+
stdinContent,
|
|
1174
|
+
root: options.root,
|
|
1175
|
+
onNotice: notice,
|
|
1176
|
+
// Only on a terminal: off one, a missing key is a refusal, not a
|
|
1177
|
+
// question nobody can answer. With `-` stdin is the page, so no
|
|
1178
|
+
// question is put either.
|
|
1179
|
+
confirm: terminalConfirm()
|
|
1180
|
+
});
|
|
1181
|
+
const message = addMessage(result);
|
|
1182
|
+
const diffs = [result.diff, result.manifest?.diff ?? ""].filter((d) => d.length > 0);
|
|
1183
|
+
if (usingStdin) {
|
|
1184
|
+
process.stdout.write(result.content);
|
|
1185
|
+
if (dryRun) for (const diff of diffs) process.stderr.write(`${diff}
|
|
1186
|
+
`);
|
|
1187
|
+
process.stderr.write(`${message}
|
|
1188
|
+
`);
|
|
1189
|
+
} else if (dryRun) {
|
|
1190
|
+
for (const diff of diffs) process.stdout.write(`${diff}
|
|
1191
|
+
`);
|
|
1192
|
+
process.stderr.write(`${message}
|
|
1193
|
+
`);
|
|
1194
|
+
} else {
|
|
1195
|
+
process.stdout.write(`${message}
|
|
1196
|
+
`);
|
|
1197
|
+
}
|
|
1198
|
+
process.exitCode = 0;
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
fail(err);
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
program.command("update").description("Rewrite moved citations in place; with --accept, re-mint changed ones").argument(
|
|
1204
|
+
"[paths...]",
|
|
1205
|
+
"files, directories, or globs to update (use - for stdin)"
|
|
1206
|
+
).option(
|
|
1207
|
+
"--collection <name>",
|
|
1208
|
+
"configured collection to run over; repeatable",
|
|
1209
|
+
collect,
|
|
1210
|
+
[]
|
|
1211
|
+
).option("--ext <list>", "comma-separated extensions for directory walks").option("--exclude <glob>", "glob to exclude; repeatable", collect, []).option("--as <format>", "force an input format for every input").option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").option("--allow-empty", "treat zero matched files as success").option("--no-gitignore", "update files .gitignore covers").option("--no-check-sources", "refused: update needs the sources").option("--root <dir>", "directory src: paths resolve from (as check)").option("--accept", "re-mint changed and never-true entries at HEAD; prints old and new pins").option("--only <id>", "limit to entries with this id; repeatable", collect, []).option("--dry-run", "print the diffs; write nothing").option(
|
|
1212
|
+
"-f, --format <format>",
|
|
1213
|
+
`output: ${UPDATE_FORMATS.join(" | ")}`,
|
|
1214
|
+
"pretty"
|
|
1215
|
+
).addHelpText(
|
|
1216
|
+
"after",
|
|
1217
|
+
[
|
|
1218
|
+
"",
|
|
1219
|
+
"Examples:",
|
|
1220
|
+
" manni cite update docs/ # rewrite moved entries",
|
|
1221
|
+
" manni cite update --accept --only fetch-timeout docs/limits.md",
|
|
1222
|
+
" manni cite update --dry-run docs/ # see the diffs first"
|
|
1223
|
+
].join("\n")
|
|
1224
|
+
).action(async (paths, options, command) => {
|
|
1225
|
+
try {
|
|
1226
|
+
const format = options.format;
|
|
1227
|
+
if (!isUpdateFormat(format)) {
|
|
1228
|
+
throw new CiteError(
|
|
1229
|
+
`Unknown --format "${format}". Use ${COMMON_FORMAT_LIST}.`
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
if (!options.checkSources) {
|
|
1233
|
+
throw new CiteError(
|
|
1234
|
+
"update needs the sources: drop --no-check-sources (or `checkSources: false`)."
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const exts = options.ext ? splitList(options.ext) : void 0;
|
|
1238
|
+
const usingStdin = paths.includes(STDIN_TOKEN);
|
|
1239
|
+
const stdinContent = usingStdin ? await readStdin() : void 0;
|
|
1240
|
+
const dryRun = Boolean(options.dryRun);
|
|
1241
|
+
const pageToStdout = usingStdin && !dryRun;
|
|
1242
|
+
const cwd = process.cwd();
|
|
1243
|
+
const run = await runUpdate({
|
|
1244
|
+
inputs: paths,
|
|
1245
|
+
collection: options.collection,
|
|
1246
|
+
exts,
|
|
1247
|
+
exclude: options.exclude,
|
|
1248
|
+
as: options.as,
|
|
1249
|
+
...configOption(options.config),
|
|
1250
|
+
onConfigLoaded: reportConfig(format === "pretty" && !pageToStdout, cwd),
|
|
1251
|
+
stdinContent,
|
|
1252
|
+
allowEmpty: options.allowEmpty ? true : void 0,
|
|
1253
|
+
respectGitignore: explicitFalse(options.gitignore),
|
|
1254
|
+
onNotice: notice,
|
|
1255
|
+
root: options.root,
|
|
1256
|
+
accept: options.accept ? true : void 0,
|
|
1257
|
+
only: options.only.length > 0 ? options.only : void 0,
|
|
1258
|
+
dryRun
|
|
1259
|
+
});
|
|
1260
|
+
const text = format === "json" ? renderUpdateJson(run) : renderUpdatePretty(run, {
|
|
1261
|
+
color: colorFor(command, process.stdout.isTTY),
|
|
1262
|
+
// The diffs are the point of a dry run; a real run prints
|
|
1263
|
+
// what it rewrote and leaves the file to say the rest.
|
|
1264
|
+
showDiff: dryRun
|
|
1265
|
+
});
|
|
1266
|
+
const stdinPage = pageToStdout ? run.pages.find((page) => page.content !== void 0) : void 0;
|
|
1267
|
+
if (stdinPage?.content !== void 0) {
|
|
1268
|
+
process.stdout.write(stdinPage.content);
|
|
1269
|
+
process.stderr.write(`${text}
|
|
1270
|
+
`);
|
|
1271
|
+
} else {
|
|
1272
|
+
process.stdout.write(`${text}
|
|
1273
|
+
`);
|
|
1274
|
+
}
|
|
1275
|
+
process.exitCode = run.exitCode;
|
|
1276
|
+
} catch (err) {
|
|
1277
|
+
fail(err);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
return program;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/key/cli.ts
|
|
1284
|
+
import { Command as Command3 } from "commander";
|
|
1285
|
+
|
|
1286
|
+
// src/key/reporters/json.ts
|
|
1287
|
+
function renderRotateJson(result) {
|
|
1288
|
+
return JSON.stringify(
|
|
1289
|
+
{
|
|
1290
|
+
pages: result.pages,
|
|
1291
|
+
manifests: result.manifests,
|
|
1292
|
+
reencrypted: result.reencrypted,
|
|
1293
|
+
skipped: result.skipped,
|
|
1294
|
+
keyWritten: result.keyWritten
|
|
1295
|
+
},
|
|
1296
|
+
null,
|
|
1297
|
+
2
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/key/reporters/pretty.ts
|
|
1302
|
+
var BASELINE_NOTICE = "The citation baseline fingerprints id-less encrypted citations by their pin; re-record it with `manni cite check --write-baseline`.";
|
|
1303
|
+
var NARROWED = "Key not written: this run covered part of the family. Finish with a whole run under the same key: `manni key rotate --to <the same value>`.";
|
|
1304
|
+
var FROM_ENV = "Key not written: it comes from MANNI_ENCRYPTION_KEY. Update the secret to the value you passed.";
|
|
1305
|
+
var plural2 = (n, one, many = `${one}s`) => `${String(n)} ${n === 1 ? one : many}`;
|
|
1306
|
+
function setMessage(result) {
|
|
1307
|
+
return result.dryRun ? `Would write encryptionKey to ${result.source}.` : `Encryption key written to ${result.source}.`;
|
|
1308
|
+
}
|
|
1309
|
+
function shortCiphertext(value) {
|
|
1310
|
+
const colon = value.indexOf(":");
|
|
1311
|
+
const token = colon === -1 ? value : value.slice(0, colon);
|
|
1312
|
+
const suffix = colon === -1 ? "" : value.slice(colon);
|
|
1313
|
+
return `${token.slice(0, 5)}\u2026${suffix}`;
|
|
1314
|
+
}
|
|
1315
|
+
function nameOf(row) {
|
|
1316
|
+
if (row.kind === "metadata") return row.pointer;
|
|
1317
|
+
if (row.id !== void 0) return row.id;
|
|
1318
|
+
if (row.index !== void 0) return `/citations/${String(row.index)}`;
|
|
1319
|
+
return `line ${String(row.line ?? 0)}`;
|
|
1320
|
+
}
|
|
1321
|
+
function closing(result) {
|
|
1322
|
+
switch (result.outcome) {
|
|
1323
|
+
case "written":
|
|
1324
|
+
return result.configSource === void 0 ? void 0 : `Encryption key written to ${result.configSource}.`;
|
|
1325
|
+
case "finished":
|
|
1326
|
+
return void 0;
|
|
1327
|
+
case "narrowed":
|
|
1328
|
+
return NARROWED;
|
|
1329
|
+
case "env":
|
|
1330
|
+
return FROM_ENV;
|
|
1331
|
+
case "skipped":
|
|
1332
|
+
return result.skipped === 1 ? "Key not written: 1 value could not be re-encrypted. Fix it and rotate again." : `Key not written: ${String(result.skipped)} values could not be re-encrypted. Fix them and rotate again.`;
|
|
1333
|
+
case "dry-run":
|
|
1334
|
+
return "Dry run: nothing written.";
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function renderRotatePretty(result, opts) {
|
|
1338
|
+
const c = palette(opts.color);
|
|
1339
|
+
const lines = [];
|
|
1340
|
+
for (const page of result.pages) {
|
|
1341
|
+
for (const row of page.rewritten) {
|
|
1342
|
+
const gap = row.kind === "metadata" ? " " : " ";
|
|
1343
|
+
lines.push(
|
|
1344
|
+
`${page.file}: ${nameOf(row)} ${c.dim(shortCiphertext(row.from))}${gap}-> ${shortCiphertext(row.to)}`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
for (const row of page.skipped) {
|
|
1348
|
+
lines.push(`${page.file}: ${nameOf(row)} ${c.red("skipped:")} ${row.message}`);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
for (const manifest of result.manifests) {
|
|
1352
|
+
for (const row of manifest.rewritten) {
|
|
1353
|
+
lines.push(
|
|
1354
|
+
`${manifest.file}: ${row.entry}${row.pointer} ${c.dim(shortCiphertext(row.from))} -> ${shortCiphertext(row.to)}`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
for (const row of manifest.skipped) {
|
|
1358
|
+
lines.push(
|
|
1359
|
+
`${manifest.file}: ${row.entry}${row.pointer} ${c.red("skipped:")} ${row.message}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (result.outcome === "finished" && result.configSource !== void 0) {
|
|
1364
|
+
lines.push(`Finished the interrupted rotation in ${result.configSource}.`);
|
|
1365
|
+
}
|
|
1366
|
+
const files = result.pages.filter((page) => page.rewritten.length > 0).length + result.manifests.filter((manifest) => manifest.rewritten.length > 0).length;
|
|
1367
|
+
lines.push(
|
|
1368
|
+
`${plural2(result.reencrypted, "value")} re-encrypted in ${plural2(files, "file")}, ${String(result.skipped)} skipped`
|
|
1369
|
+
);
|
|
1370
|
+
const last = closing(result);
|
|
1371
|
+
if (last !== void 0) lines.push(result.outcome === "skipped" ? c.red(last) : last);
|
|
1372
|
+
if (result.baselineStale) lines.push(BASELINE_NOTICE);
|
|
1373
|
+
return lines.join("\n");
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// src/key/cli.ts
|
|
1377
|
+
var ROTATE_FORMATS = ["pretty", "json"];
|
|
1378
|
+
function isRotateFormat(value) {
|
|
1379
|
+
return ROTATE_FORMATS.includes(value);
|
|
1380
|
+
}
|
|
1381
|
+
function buildProgram4() {
|
|
1382
|
+
const program = new Command3();
|
|
1383
|
+
program.name("key").description("Set and rotate the family key that encrypted values are encrypted with.").version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
|
|
1384
|
+
const color = () => shouldColor({ noColor: program.opts().color === false, isTTY: process.stdout.isTTY });
|
|
1385
|
+
program.command("set").description("Write the family encryption key to manni.config.yaml; generate one when no value is given").argument("[value]", "the key, at least 32 hex or base64url characters (default: 64 random hex)").option(
|
|
1386
|
+
"-c, --config <path>",
|
|
1387
|
+
"the config file to write (default: the nearest manni.config.yaml, else a new one at the git root)"
|
|
1388
|
+
).option("--dry-run", "say what would be written; write nothing").addHelpText(
|
|
1389
|
+
"after",
|
|
1390
|
+
[
|
|
1391
|
+
"",
|
|
1392
|
+
"Examples:",
|
|
1393
|
+
" manni key set # generate 256 bits into manni.config.yaml",
|
|
1394
|
+
" manni key set --dry-run # name the file, write nothing",
|
|
1395
|
+
" manni key set -c ops/manni.config.yaml 0123456789abcdef0123456789abcdef"
|
|
1396
|
+
].join("\n")
|
|
1397
|
+
).action(async (value, options) => {
|
|
1398
|
+
try {
|
|
1399
|
+
const result = await runKeySet({
|
|
1400
|
+
value,
|
|
1401
|
+
configPath: options.config,
|
|
1402
|
+
dryRun: Boolean(options.dryRun),
|
|
1403
|
+
onNotice: notice
|
|
1404
|
+
});
|
|
1405
|
+
process.stdout.write(`${setMessage(result)}
|
|
1406
|
+
`);
|
|
1407
|
+
process.exitCode = 0;
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
fail(err);
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
program.command("rotate").description("Re-encrypt every encrypted value under a new key, then write the key").argument("[paths...]", "files, directories, or globs (default: every collection)").option(
|
|
1413
|
+
"--to <value>",
|
|
1414
|
+
"the new key (default: 64 random hex); required for a narrowed run and a key from MANNI_ENCRYPTION_KEY"
|
|
1415
|
+
).option("--collection <name>", "configured collection to run over; repeatable", collect, []).option("--ext <list>", "comma-separated extensions for directory walks").option("--exclude <glob>", "glob to exclude; repeatable", collect, []).option("--as <format>", "force an input format for every input").option("-c, --config <path>", "path to a manni config file").option("--allow-empty", "treat zero matched files as success").option("--no-gitignore", "include files .gitignore covers").option(
|
|
1416
|
+
"--root <dir>",
|
|
1417
|
+
"directory cited sources resolve from (default: cite.root from config, else the git root)"
|
|
1418
|
+
).option("--dry-run", "print what would change; write nothing").option("-f, --format <format>", `output: ${ROTATE_FORMATS.join(" | ")}`, "pretty").addHelpText(
|
|
1419
|
+
"after",
|
|
1420
|
+
[
|
|
1421
|
+
"",
|
|
1422
|
+
"Examples:",
|
|
1423
|
+
" manni key rotate # every collection, then the key",
|
|
1424
|
+
" manni key rotate --dry-run # see every change first",
|
|
1425
|
+
' manni key rotate --collection site --to "$NEW_KEY" # part of the family',
|
|
1426
|
+
' MANNI_ENCRYPTION_KEY=\u2026 manni key rotate --to "$NEW_KEY"'
|
|
1427
|
+
].join("\n")
|
|
1428
|
+
).action(async (paths, options) => {
|
|
1429
|
+
try {
|
|
1430
|
+
const format = options.format;
|
|
1431
|
+
if (!isRotateFormat(format)) {
|
|
1432
|
+
throw new KeyError(`Unknown --format "${format}". Use ${COMMON_FORMAT_LIST}.`);
|
|
1433
|
+
}
|
|
1434
|
+
const result = await runKeyRotate({
|
|
1435
|
+
inputs: paths,
|
|
1436
|
+
collection: options.collection,
|
|
1437
|
+
to: options.to,
|
|
1438
|
+
exts: options.ext ? splitList(options.ext) : void 0,
|
|
1439
|
+
exclude: options.exclude,
|
|
1440
|
+
as: options.as,
|
|
1441
|
+
configPath: options.config,
|
|
1442
|
+
// `undefined` rather than `false` when absent.
|
|
1443
|
+
allowEmpty: options.allowEmpty ? true : void 0,
|
|
1444
|
+
respectGitignore: explicitFalse(options.gitignore),
|
|
1445
|
+
root: options.root,
|
|
1446
|
+
dryRun: Boolean(options.dryRun),
|
|
1447
|
+
onNotice: notice
|
|
1448
|
+
});
|
|
1449
|
+
if (format === "json") {
|
|
1450
|
+
process.stdout.write(`${renderRotateJson(result)}
|
|
1451
|
+
`);
|
|
1452
|
+
if (result.baselineStale) notice(BASELINE_NOTICE);
|
|
1453
|
+
} else {
|
|
1454
|
+
process.stdout.write(`${renderRotatePretty(result, { color: color() })}
|
|
1455
|
+
`);
|
|
1456
|
+
}
|
|
1457
|
+
process.exitCode = result.exitCode;
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
fail(err);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
return program;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/cli.ts
|
|
1466
|
+
function buildProgram5() {
|
|
944
1467
|
const meta = buildProgram().name("meta").description(
|
|
945
1468
|
"Validate, read, query and fill document metadata against JSON Schema"
|
|
946
1469
|
);
|
|
947
1470
|
const metaCommands = new Set(meta.commands.map((c) => c.name()));
|
|
948
|
-
const program = new
|
|
1471
|
+
const program = new Command4();
|
|
949
1472
|
program.name("manni").description("Tools for documentation that is meant to be checked.").version(package_default.version, "-V, --version").showHelpAfterError("(add --help for usage)").exitOverride().configureOutput({
|
|
950
1473
|
outputError: (str, write) => {
|
|
951
1474
|
write(str);
|
|
@@ -965,10 +1488,18 @@ function buildProgram3() {
|
|
|
965
1488
|
"Crawl a site and check every page for accessibility violations with axe-core"
|
|
966
1489
|
)
|
|
967
1490
|
);
|
|
1491
|
+
program.addCommand(
|
|
1492
|
+
buildProgram3().name("cite").description(
|
|
1493
|
+
"Track citations from doc claims to source lines and check them for drift"
|
|
1494
|
+
)
|
|
1495
|
+
);
|
|
1496
|
+
program.addCommand(
|
|
1497
|
+
buildProgram4().name("key").description("Set and rotate the family key that encrypted values are encrypted with.")
|
|
1498
|
+
);
|
|
968
1499
|
return program;
|
|
969
1500
|
}
|
|
970
|
-
runIfMain(import.meta.url,
|
|
1501
|
+
runIfMain(import.meta.url, buildProgram5);
|
|
971
1502
|
export {
|
|
972
|
-
|
|
1503
|
+
buildProgram5 as buildProgram
|
|
973
1504
|
};
|
|
974
1505
|
//# sourceMappingURL=cli.js.map
|