@hawkeyexl/manni 4.9.0 → 4.11.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 +1 -1
- package/dist/{batch-W5GXL2P4.js → batch-UAULUMGH.js} +18 -14
- package/dist/{build-Q56FEXR6.js → build-QSPNK5AL.js} +14 -11
- package/dist/calibrate-KXN4URPM.js +40 -0
- package/dist/{capture-GMYH4ATL.js → capture-JHTSUPUC.js} +16 -13
- package/dist/{check-OAJWC3WD.js → check-MDGXEUWL.js} +5 -5
- package/dist/{check-KUBELO5G.js → check-VELUQAUW.js} +17 -14
- package/dist/{check-KUBELO5G.js.map → check-VELUQAUW.js.map} +1 -1
- package/dist/check-WOQRKNZK.js +29 -0
- package/dist/{check-ZE5HIDOW.js → check-YEOYBFST.js} +14 -11
- package/dist/{chunk-FGUXMZXO.js → chunk-2DRHT73U.js} +7 -7
- package/dist/{chunk-XHFHXOQQ.js → chunk-2IAZEB45.js} +8 -8
- package/dist/{chunk-35WTFPQS.js → chunk-2IR4T5PO.js} +2 -2
- package/dist/{chunk-KX5IYIOO.js → chunk-2MJ62AYQ.js} +2 -2
- package/dist/chunk-2MJ62AYQ.js.map +1 -0
- package/dist/{chunk-OWSNXPPX.js → chunk-4CK6ZSOK.js} +6 -6
- package/dist/{chunk-YUZ2ECZY.js → chunk-4D5IZSXM.js} +3 -3
- package/dist/{chunk-RFSGKF4O.js → chunk-4VBSTXN6.js} +4 -4
- package/dist/{chunk-3XMG4CZY.js → chunk-54F74XR3.js} +15 -13
- package/dist/{chunk-3XMG4CZY.js.map → chunk-54F74XR3.js.map} +1 -1
- package/dist/{chunk-IN6DWNVE.js → chunk-6HWBRZIN.js} +47 -191
- package/dist/chunk-6HWBRZIN.js.map +1 -0
- package/dist/{chunk-GEMXGXPR.js → chunk-ADMEPUBT.js} +3 -3
- package/dist/{chunk-23UAGEKP.js → chunk-B7XYNRJQ.js} +2 -2
- package/dist/{chunk-QKE3XS2L.js → chunk-BBT6AVUG.js} +2 -2
- package/dist/{chunk-VKWZCEWB.js → chunk-BKVHX6FU.js} +4 -4
- package/dist/{chunk-X6WLCNKC.js → chunk-D4N2V43Z.js} +4 -4
- package/dist/{chunk-XXDXIIQ4.js → chunk-EFW7CAG4.js} +2 -2
- package/dist/{chunk-UDUHRXJ4.js → chunk-EIFCP6CZ.js} +2 -2
- package/dist/{chunk-KPXOJMQ6.js → chunk-FBMZJEB3.js} +2 -2
- package/dist/{chunk-HRXDM6H6.js → chunk-FQXWVS6J.js} +8 -8
- package/dist/{chunk-76I6I5N2.js → chunk-FUCPZVRY.js} +2 -2
- package/dist/{chunk-DTGIUMZV.js → chunk-GDVZ5VII.js} +4 -4
- package/dist/{chunk-QCCAHV6A.js → chunk-GV56WGL2.js} +321 -3308
- package/dist/chunk-GV56WGL2.js.map +1 -0
- package/dist/{chunk-TKTRIZT4.js → chunk-HA6MQMOY.js} +6 -6
- package/dist/{chunk-ECVQNGTH.js → chunk-HQR2ZBHW.js} +3 -3
- package/dist/{chunk-MHAYLDBK.js → chunk-IGSHYP74.js} +4 -4
- package/dist/{chunk-YEUQ7OKT.js → chunk-J6IDQGJL.js} +4 -4
- package/dist/{chunk-5LBEQ4ZU.js → chunk-JPQDNYXH.js} +2 -2
- package/dist/{chunk-6KKZXB2O.js → chunk-JROBZ7JA.js} +2 -2
- package/dist/{chunk-TMI2YF6D.js → chunk-KGEQJ5CC.js} +15 -26
- package/dist/{chunk-TMI2YF6D.js.map → chunk-KGEQJ5CC.js.map} +1 -1
- package/dist/{chunk-2EHDJ47Y.js → chunk-LBSCW232.js} +4 -4
- package/dist/{chunk-M6GH3FGJ.js → chunk-LKP7QDFN.js} +4 -4
- package/dist/{chunk-5EB6RIXJ.js → chunk-LPIF5LIE.js} +2 -2
- package/dist/chunk-M42DZCOU.js +164 -0
- package/dist/chunk-M42DZCOU.js.map +1 -0
- package/dist/{chunk-ND2S5BXB.js → chunk-MSRU4D4P.js} +2 -2
- package/dist/{chunk-BSTXARMY.js → chunk-NSBXVPZL.js} +11 -11
- package/dist/{chunk-Q45PZX2D.js → chunk-ODRPL3WB.js} +2 -2
- package/dist/{chunk-FSBIPCWE.js → chunk-OPXWPJDA.js} +10 -10
- package/dist/chunk-PCQ7ELJH.js +3081 -0
- package/dist/chunk-PCQ7ELJH.js.map +1 -0
- package/dist/{chunk-QAKJNPY3.js → chunk-PCS4VJBV.js} +6 -6
- package/dist/{chunk-GRILZZGS.js → chunk-PWBH7DCQ.js} +3 -3
- package/dist/{chunk-XKBEHKWO.js → chunk-QLWC7MKD.js} +34 -32
- package/dist/chunk-QLWC7MKD.js.map +1 -0
- package/dist/{chunk-XTAHZTES.js → chunk-R4WXQIOZ.js} +6 -6
- package/dist/{chunk-3CDZWY6M.js → chunk-RLT5SEML.js} +5 -5
- package/dist/{chunk-XG6PVB5S.js → chunk-SSQZB5SQ.js} +1 -11
- package/dist/chunk-SSQZB5SQ.js.map +1 -0
- package/dist/chunk-T7JAOYE5.js +393 -0
- package/dist/chunk-T7JAOYE5.js.map +1 -0
- package/dist/chunk-TU2WUBVZ.js +2 -0
- package/dist/{chunk-Q2H7J3FF.js → chunk-U7CTFN2J.js} +12 -10
- package/dist/{chunk-Q2H7J3FF.js.map → chunk-U7CTFN2J.js.map} +1 -1
- package/dist/{chunk-3XGGF5JW.js → chunk-VSEBFHAY.js} +10 -7
- package/dist/{chunk-3XGGF5JW.js.map → chunk-VSEBFHAY.js.map} +1 -1
- package/dist/{chunk-OVOYKWCM.js → chunk-WDWXAYVH.js} +4 -4
- package/dist/{chunk-5WME4CB6.js → chunk-Y6DOMTQD.js} +10 -8
- package/dist/{chunk-5WME4CB6.js.map → chunk-Y6DOMTQD.js.map} +1 -1
- package/dist/chunk-ZMECS2IJ.js +27 -0
- package/dist/chunk-ZMECS2IJ.js.map +1 -0
- package/dist/chunk-ZPLVANY2.js +15 -0
- package/dist/chunk-ZPLVANY2.js.map +1 -0
- package/dist/{cli-MEPJTS2V.js → cli-3BTCK4AB.js} +18 -15
- package/dist/{cli-MEPJTS2V.js.map → cli-3BTCK4AB.js.map} +1 -1
- package/dist/{cli-EMQXRN6E.js → cli-4MF37HAQ.js} +16 -12
- package/dist/{cli-EMQXRN6E.js.map → cli-4MF37HAQ.js.map} +1 -1
- package/dist/{cli-YDMMIL2U.js → cli-6KZTHWGP.js} +17 -14
- package/dist/{cli-YDMMIL2U.js.map → cli-6KZTHWGP.js.map} +1 -1
- package/dist/{cli-ZJRHU7MO.js → cli-DRMZBZLB.js} +39 -34
- package/dist/cli-DRMZBZLB.js.map +1 -0
- package/dist/{cli-J35OPSTP.js → cli-I62SRH3R.js} +10 -8
- package/dist/cli-JJMOVIKS.js +83 -0
- package/dist/cli-JJMOVIKS.js.map +1 -0
- package/dist/{cli-TPONWND4.js → cli-O4ABTN4Z.js} +25 -21
- package/dist/{cli-TPONWND4.js.map → cli-O4ABTN4Z.js.map} +1 -1
- package/dist/{cli-URNPOMOH.js → cli-ONTC5PKT.js} +3 -3
- package/dist/{cli-BJ7JXJ2U.js → cli-RORDPAPE.js} +17 -14
- package/dist/{cli-BJ7JXJ2U.js.map → cli-RORDPAPE.js.map} +1 -1
- package/dist/{cli-B7SFJDZA.js → cli-VID7PJS3.js} +17 -13
- package/dist/{cli-B7SFJDZA.js.map → cli-VID7PJS3.js.map} +1 -1
- package/dist/cli-VUKMWSSF.js +839 -0
- package/dist/cli-VUKMWSSF.js.map +1 -0
- package/dist/cli.js +24 -11
- package/dist/cli.js.map +1 -1
- package/dist/{config-YWSNBFEE.js → config-XDA42RJL.js} +3 -3
- package/dist/docmeta.js +9 -7
- package/dist/docmeta.js.map +1 -1
- package/dist/github-RY3FRA6P.js +29 -0
- package/dist/github-YKQSH7FS.js +34 -0
- package/dist/index.d.ts +1023 -915
- package/dist/index.js +107 -81
- package/dist/index.js.map +1 -1
- package/dist/{json-JIUPGOUQ.js → json-BXY44ED7.js} +13 -10
- package/dist/lint-YJ2L22BW.js +33 -0
- package/dist/list-NSG24VVX.js +31 -0
- package/dist/list-V6EGCSHX.js +34 -0
- package/dist/{page-WQ3GQF76.js → page-EV7OUBAU.js} +12 -9
- package/dist/{prepare-ZUTYXTUZ.js → prepare-JHJDJB6I.js} +17 -14
- package/dist/{prepare-ZUTYXTUZ.js.map → prepare-JHJDJB6I.js.map} +1 -1
- package/dist/{pretty-Q3C433DW.js → pretty-Q5Y2PA64.js} +14 -11
- package/dist/{pretty-CBV5XSMZ.js → pretty-SCRK66M5.js} +13 -10
- package/dist/{reporters-DH5HPFXJ.js → reporters-A5ADKDYR.js} +12 -9
- package/dist/{reporters-TTYGOUAF.js → reporters-RBYTASMI.js} +5 -5
- package/dist/run-BUE2DDXN.js +36 -0
- package/dist/{term-DL3YFL43.js → term-CQAGNG3Q.js} +17 -14
- package/dist/{term-DL3YFL43.js.map → term-CQAGNG3Q.js.map} +1 -1
- package/dist/{validate-UVDMC5LT.js → validate-ZBW44RHT.js} +6 -5
- package/dist/validate-ZBW44RHT.js.map +1 -0
- package/package.json +1 -1
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/dist/calibrate-XFE56P4M.js +0 -36
- package/dist/check-HBRX4FOR.js +0 -26
- package/dist/chunk-IN6DWNVE.js.map +0 -1
- package/dist/chunk-KX5IYIOO.js.map +0 -1
- package/dist/chunk-QCCAHV6A.js.map +0 -1
- package/dist/chunk-XG6PVB5S.js.map +0 -1
- package/dist/chunk-XKBEHKWO.js.map +0 -1
- package/dist/cli-ZJRHU7MO.js.map +0 -1
- package/dist/github-63CZJGZS.js +0 -31
- package/dist/github-IYOVHWHG.js +0 -26
- package/dist/lint-TBE35CCW.js +0 -29
- package/dist/list-IZ4RIM72.js +0 -30
- package/dist/list-TNEJ3J77.js +0 -28
- package/dist/run-LZXKVTM7.js +0 -32
- /package/dist/{batch-W5GXL2P4.js.map → batch-UAULUMGH.js.map} +0 -0
- /package/dist/{build-Q56FEXR6.js.map → build-QSPNK5AL.js.map} +0 -0
- /package/dist/{calibrate-XFE56P4M.js.map → calibrate-KXN4URPM.js.map} +0 -0
- /package/dist/{capture-GMYH4ATL.js.map → capture-JHTSUPUC.js.map} +0 -0
- /package/dist/{check-HBRX4FOR.js.map → check-MDGXEUWL.js.map} +0 -0
- /package/dist/{check-OAJWC3WD.js.map → check-WOQRKNZK.js.map} +0 -0
- /package/dist/{check-ZE5HIDOW.js.map → check-YEOYBFST.js.map} +0 -0
- /package/dist/{chunk-FGUXMZXO.js.map → chunk-2DRHT73U.js.map} +0 -0
- /package/dist/{chunk-XHFHXOQQ.js.map → chunk-2IAZEB45.js.map} +0 -0
- /package/dist/{chunk-35WTFPQS.js.map → chunk-2IR4T5PO.js.map} +0 -0
- /package/dist/{chunk-OWSNXPPX.js.map → chunk-4CK6ZSOK.js.map} +0 -0
- /package/dist/{chunk-YUZ2ECZY.js.map → chunk-4D5IZSXM.js.map} +0 -0
- /package/dist/{chunk-RFSGKF4O.js.map → chunk-4VBSTXN6.js.map} +0 -0
- /package/dist/{chunk-GEMXGXPR.js.map → chunk-ADMEPUBT.js.map} +0 -0
- /package/dist/{chunk-23UAGEKP.js.map → chunk-B7XYNRJQ.js.map} +0 -0
- /package/dist/{chunk-QKE3XS2L.js.map → chunk-BBT6AVUG.js.map} +0 -0
- /package/dist/{chunk-VKWZCEWB.js.map → chunk-BKVHX6FU.js.map} +0 -0
- /package/dist/{chunk-X6WLCNKC.js.map → chunk-D4N2V43Z.js.map} +0 -0
- /package/dist/{chunk-XXDXIIQ4.js.map → chunk-EFW7CAG4.js.map} +0 -0
- /package/dist/{chunk-UDUHRXJ4.js.map → chunk-EIFCP6CZ.js.map} +0 -0
- /package/dist/{chunk-KPXOJMQ6.js.map → chunk-FBMZJEB3.js.map} +0 -0
- /package/dist/{chunk-HRXDM6H6.js.map → chunk-FQXWVS6J.js.map} +0 -0
- /package/dist/{chunk-76I6I5N2.js.map → chunk-FUCPZVRY.js.map} +0 -0
- /package/dist/{chunk-DTGIUMZV.js.map → chunk-GDVZ5VII.js.map} +0 -0
- /package/dist/{chunk-TKTRIZT4.js.map → chunk-HA6MQMOY.js.map} +0 -0
- /package/dist/{chunk-ECVQNGTH.js.map → chunk-HQR2ZBHW.js.map} +0 -0
- /package/dist/{chunk-MHAYLDBK.js.map → chunk-IGSHYP74.js.map} +0 -0
- /package/dist/{chunk-YEUQ7OKT.js.map → chunk-J6IDQGJL.js.map} +0 -0
- /package/dist/{chunk-5LBEQ4ZU.js.map → chunk-JPQDNYXH.js.map} +0 -0
- /package/dist/{chunk-6KKZXB2O.js.map → chunk-JROBZ7JA.js.map} +0 -0
- /package/dist/{chunk-2EHDJ47Y.js.map → chunk-LBSCW232.js.map} +0 -0
- /package/dist/{chunk-M6GH3FGJ.js.map → chunk-LKP7QDFN.js.map} +0 -0
- /package/dist/{chunk-5EB6RIXJ.js.map → chunk-LPIF5LIE.js.map} +0 -0
- /package/dist/{chunk-ND2S5BXB.js.map → chunk-MSRU4D4P.js.map} +0 -0
- /package/dist/{chunk-BSTXARMY.js.map → chunk-NSBXVPZL.js.map} +0 -0
- /package/dist/{chunk-Q45PZX2D.js.map → chunk-ODRPL3WB.js.map} +0 -0
- /package/dist/{chunk-FSBIPCWE.js.map → chunk-OPXWPJDA.js.map} +0 -0
- /package/dist/{chunk-QAKJNPY3.js.map → chunk-PCS4VJBV.js.map} +0 -0
- /package/dist/{chunk-GRILZZGS.js.map → chunk-PWBH7DCQ.js.map} +0 -0
- /package/dist/{chunk-XTAHZTES.js.map → chunk-R4WXQIOZ.js.map} +0 -0
- /package/dist/{chunk-3CDZWY6M.js.map → chunk-RLT5SEML.js.map} +0 -0
- /package/dist/{cli-J35OPSTP.js.map → chunk-TU2WUBVZ.js.map} +0 -0
- /package/dist/{chunk-OVOYKWCM.js.map → chunk-WDWXAYVH.js.map} +0 -0
- /package/dist/{config-YWSNBFEE.js.map → cli-I62SRH3R.js.map} +0 -0
- /package/dist/{cli-URNPOMOH.js.map → cli-ONTC5PKT.js.map} +0 -0
- /package/dist/{github-63CZJGZS.js.map → config-XDA42RJL.js.map} +0 -0
- /package/dist/{github-IYOVHWHG.js.map → github-RY3FRA6P.js.map} +0 -0
- /package/dist/{json-JIUPGOUQ.js.map → github-YKQSH7FS.js.map} +0 -0
- /package/dist/{lint-TBE35CCW.js.map → json-BXY44ED7.js.map} +0 -0
- /package/dist/{list-IZ4RIM72.js.map → lint-YJ2L22BW.js.map} +0 -0
- /package/dist/{list-TNEJ3J77.js.map → list-NSG24VVX.js.map} +0 -0
- /package/dist/{page-WQ3GQF76.js.map → list-V6EGCSHX.js.map} +0 -0
- /package/dist/{pretty-CBV5XSMZ.js.map → page-EV7OUBAU.js.map} +0 -0
- /package/dist/{pretty-Q3C433DW.js.map → pretty-Q5Y2PA64.js.map} +0 -0
- /package/dist/{reporters-DH5HPFXJ.js.map → pretty-SCRK66M5.js.map} +0 -0
- /package/dist/{reporters-TTYGOUAF.js.map → reporters-A5ADKDYR.js.map} +0 -0
- /package/dist/{run-LZXKVTM7.js.map → reporters-RBYTASMI.js.map} +0 -0
- /package/dist/{validate-UVDMC5LT.js.map → run-BUE2DDXN.js.map} +0 -0
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
CHECK_NAME,
|
|
4
|
-
DEFAULT_BASELINE_PATH,
|
|
5
3
|
DERIVED_KEYWORD,
|
|
6
4
|
DERIVED_STALE_SCHEMA,
|
|
7
|
-
DERIVE_SOURCES,
|
|
8
5
|
DocmetaError,
|
|
9
6
|
FILE_SCHEMA_KEY,
|
|
10
|
-
LEGACY_BASELINE_PATH,
|
|
11
7
|
ManifestCache,
|
|
12
|
-
PROVENANCE_FIELD,
|
|
13
|
-
STAMPED_DATE_FIELDS,
|
|
14
8
|
STDIN_LABEL,
|
|
15
9
|
STDIN_TOKEN,
|
|
16
|
-
assertNonEmpty,
|
|
17
10
|
blockLines,
|
|
18
|
-
canonicalSchemaRef,
|
|
19
11
|
classifyRef,
|
|
20
12
|
colOf,
|
|
21
13
|
collectSchemaPins,
|
|
22
14
|
colonEnd,
|
|
23
|
-
compareDerived,
|
|
24
15
|
contentEnd,
|
|
25
16
|
deepEqual,
|
|
26
|
-
derivableFields,
|
|
27
|
-
deriveCovers,
|
|
28
17
|
detectEol,
|
|
29
18
|
detectStyle,
|
|
30
19
|
emptySource,
|
|
@@ -35,34 +24,25 @@ import {
|
|
|
35
24
|
gitignoreOptions,
|
|
36
25
|
insertKey,
|
|
37
26
|
integrityOf,
|
|
38
|
-
isBuiltinField,
|
|
39
|
-
isErrorSeverity,
|
|
40
27
|
keyLines,
|
|
41
28
|
keyString,
|
|
42
29
|
lineEnd,
|
|
43
30
|
loadConfig,
|
|
44
31
|
loadSchema,
|
|
45
32
|
locateFrontmatter,
|
|
46
|
-
managedFields,
|
|
47
33
|
positionForFactory,
|
|
48
34
|
publishedBuiltins,
|
|
49
35
|
rangeOf,
|
|
50
36
|
rebaseConfigSchemaRefs,
|
|
51
37
|
replaceBlockValue,
|
|
52
38
|
replaceFlowValue,
|
|
53
|
-
resolveBaselineRequest,
|
|
54
39
|
resolveElements,
|
|
55
|
-
resolveRunConfig,
|
|
56
40
|
resolveSchemaSetWithSource,
|
|
57
41
|
resolveTargetSet,
|
|
58
42
|
schemaLoadOptions,
|
|
59
43
|
schemaTrustRoot,
|
|
60
|
-
settleBaseline,
|
|
61
|
-
staleFindings,
|
|
62
|
-
supportedExtensions,
|
|
63
|
-
toJsonText,
|
|
64
44
|
writeFileAtomic
|
|
65
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-VSEBFHAY.js";
|
|
66
46
|
import {
|
|
67
47
|
STDIN_LINES_MARKER
|
|
68
48
|
} from "./chunk-YEUYKR62.js";
|
|
@@ -87,104 +67,40 @@ import {
|
|
|
87
67
|
pageManifestPath,
|
|
88
68
|
parseCollections,
|
|
89
69
|
sameFile
|
|
90
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-QLWC7MKD.js";
|
|
91
71
|
import {
|
|
92
72
|
ToolError,
|
|
93
73
|
errorMessage
|
|
94
74
|
} from "./chunk-G3T47CFV.js";
|
|
95
75
|
|
|
96
|
-
// src/
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
101
|
-
import { isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
102
|
-
import {
|
|
103
|
-
LineCounter,
|
|
104
|
-
isMap as isMap2,
|
|
105
|
-
isNode,
|
|
106
|
-
isScalar,
|
|
107
|
-
isSeq,
|
|
108
|
-
parseDocument as parseDocument2
|
|
109
|
-
} from "yaml";
|
|
110
|
-
|
|
111
|
-
// src/shared/manifest-cas.ts
|
|
112
|
-
import { readFile } from "node:fs/promises";
|
|
113
|
-
import { posix } from "node:path";
|
|
114
|
-
var COMMIT_ATTEMPTS = 5;
|
|
115
|
-
function conflictRefusal(file) {
|
|
116
|
-
return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
|
|
117
|
-
}
|
|
118
|
-
function heldManifest(path, file, before, absent = false) {
|
|
119
|
-
return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
|
|
120
|
-
}
|
|
121
|
-
function encodeValue(value) {
|
|
122
|
-
return value === void 0 ? "absent" : JSON.stringify(value);
|
|
123
|
-
}
|
|
124
|
-
function baseKey(at2) {
|
|
125
|
-
const entry = at2.join === "path" ? posix.normalize(at2.entry) : at2.entry;
|
|
126
|
-
return `${entry}\0${at2.key}`;
|
|
127
|
-
}
|
|
128
|
-
function rebaseable(held, op, codec) {
|
|
129
|
-
const at2 = baseKey(op);
|
|
130
|
-
if (held.bases.has(at2)) return;
|
|
131
|
-
held.bases.set(at2, {
|
|
132
|
-
entry: op.entry,
|
|
133
|
-
key: op.key,
|
|
134
|
-
join: op.join,
|
|
135
|
-
value: encodeValue(codec.read(held.before, op))
|
|
136
|
-
});
|
|
76
|
+
// src/shared/cli-options.ts
|
|
77
|
+
import { basename, relative } from "node:path";
|
|
78
|
+
function collect(value, prev) {
|
|
79
|
+
return prev.concat([value]);
|
|
137
80
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
|
|
141
|
-
}
|
|
142
|
-
let text2 = current;
|
|
143
|
-
for (const op of held.ops) text2 = codec.apply(text2, op);
|
|
144
|
-
return text2;
|
|
81
|
+
function splitList(value) {
|
|
82
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
145
83
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
84
|
+
var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
|
|
85
|
+
async function readStdin() {
|
|
86
|
+
const chunks = [];
|
|
87
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
88
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
151
89
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
current = await io.read(held.path);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (held.absent && isMissing(error)) {
|
|
159
|
-
await io.write(held.path, held.text);
|
|
160
|
-
held.absent = false;
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
164
|
-
throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
|
|
165
|
-
}
|
|
166
|
-
if (!held.absent && current === held.before) {
|
|
167
|
-
await io.write(held.path, held.text);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const replayed = rebase(held, current, io.codec);
|
|
171
|
-
if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
|
|
172
|
-
held.before = current;
|
|
173
|
-
held.text = replayed;
|
|
174
|
-
held.absent = false;
|
|
175
|
-
}
|
|
176
|
-
throw io.toError(conflictRefusal(held.file));
|
|
90
|
+
function configOption(value) {
|
|
91
|
+
if (value === false) return { noConfig: true };
|
|
92
|
+
return typeof value === "string" ? { configPath: value } : {};
|
|
177
93
|
}
|
|
178
|
-
function
|
|
179
|
-
return
|
|
94
|
+
function explicitFalse(value) {
|
|
95
|
+
return value === false ? false : void 0;
|
|
180
96
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
97
|
+
function reportConfig(toStdout, cwd) {
|
|
98
|
+
return (info) => {
|
|
99
|
+
const where = (relative(cwd, info.dir) || ".").replace(/\\/g, "/");
|
|
100
|
+
const line = `Using ${basename(info.path)} (${where})
|
|
101
|
+
`;
|
|
102
|
+
(toStdout ? process.stdout : process.stderr).write(line);
|
|
103
|
+
};
|
|
188
104
|
}
|
|
189
105
|
|
|
190
106
|
// src/meta/core/external-metadata-fetch.ts
|
|
@@ -280,29 +196,29 @@ async function readCapped(url, res, maxBytes) {
|
|
|
280
196
|
|
|
281
197
|
// src/shared/encryption-key.ts
|
|
282
198
|
import { spawnSync } from "node:child_process";
|
|
283
|
-
import { readFile
|
|
284
|
-
import { basename as
|
|
199
|
+
import { readFile } from "node:fs/promises";
|
|
200
|
+
import { basename as basename3, dirname as dirname2, join as join2, relative as relative2, resolve } from "node:path";
|
|
285
201
|
import { isDeepStrictEqual } from "node:util";
|
|
286
202
|
import { isMap, parseDocument, parse as parseYaml, stringify } from "yaml";
|
|
287
203
|
|
|
288
204
|
// src/shared/write-file.ts
|
|
289
205
|
import { chmod, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
290
|
-
import { basename, dirname, join } from "node:path";
|
|
206
|
+
import { basename as basename2, dirname, join } from "node:path";
|
|
291
207
|
var LOCKED = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
292
208
|
var RENAME_ATTEMPTS = 3;
|
|
293
209
|
function errorCode(err) {
|
|
294
210
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
295
211
|
}
|
|
296
|
-
var wait = (ms) => new Promise((
|
|
297
|
-
setTimeout(
|
|
212
|
+
var wait = (ms) => new Promise((resolve4) => {
|
|
213
|
+
setTimeout(resolve4, ms);
|
|
298
214
|
});
|
|
299
|
-
async function writeTextAtomic(path,
|
|
215
|
+
async function writeTextAtomic(path, text) {
|
|
300
216
|
const tmp = join(
|
|
301
217
|
dirname(path),
|
|
302
|
-
`.${
|
|
218
|
+
`.${basename2(path)}.manni-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`
|
|
303
219
|
);
|
|
304
220
|
try {
|
|
305
|
-
await writeFile(tmp,
|
|
221
|
+
await writeFile(tmp, text, "utf8");
|
|
306
222
|
try {
|
|
307
223
|
const { mode } = await stat(path);
|
|
308
224
|
await chmod(tmp, mode);
|
|
@@ -343,14 +259,14 @@ function resolveEncryptionKey(opts) {
|
|
|
343
259
|
return { key: void 0, source: "none" };
|
|
344
260
|
}
|
|
345
261
|
function relativeSource(cwd, path) {
|
|
346
|
-
return
|
|
262
|
+
return relative2(resolve(cwd), path).replace(/\\/g, "/");
|
|
347
263
|
}
|
|
348
264
|
function errorCode2(err) {
|
|
349
265
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
350
266
|
}
|
|
351
267
|
async function readIfPresent(path) {
|
|
352
268
|
try {
|
|
353
|
-
return await
|
|
269
|
+
return await readFile(path, "utf8");
|
|
354
270
|
} catch (err) {
|
|
355
271
|
if (errorCode2(err) === "ENOENT") return null;
|
|
356
272
|
throw err;
|
|
@@ -362,21 +278,21 @@ async function fileTarget(file, toError) {
|
|
|
362
278
|
`${file.source} is a single-tool config file, which cannot hold the family ${ENCRYPTION_KEY_FIELD}. Move its keys under the tool's section in ${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}, or set ${ENCRYPTION_KEY_ENV}.`
|
|
363
279
|
);
|
|
364
280
|
}
|
|
365
|
-
const
|
|
366
|
-
return { path: file.path, source: file.source, text:
|
|
281
|
+
const text = await readIfPresent(file.path);
|
|
282
|
+
return { path: file.path, source: file.source, text: text ?? file.text };
|
|
367
283
|
}
|
|
368
284
|
async function defaultTarget(cwd) {
|
|
369
285
|
const root = findGitRoot(cwd) ?? resolve(cwd);
|
|
370
286
|
for (const name of FAMILY_CONFIG_NAMES) {
|
|
371
287
|
const path2 = join2(root, name);
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
288
|
+
const text = await readIfPresent(path2);
|
|
289
|
+
if (text !== null) return { path: path2, source: relativeSource(cwd, path2), text };
|
|
374
290
|
}
|
|
375
291
|
const path = join2(root, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml");
|
|
376
292
|
return { path, source: relativeSource(cwd, path), text: null };
|
|
377
293
|
}
|
|
378
|
-
function withKey(
|
|
379
|
-
const doc = parseDocument(
|
|
294
|
+
function withKey(text, key, previous, source, toError) {
|
|
295
|
+
const doc = parseDocument(text);
|
|
380
296
|
const [first] = doc.errors;
|
|
381
297
|
if (first !== void 0) {
|
|
382
298
|
throw toError(`${source}: invalid YAML: ${first.message}`);
|
|
@@ -421,16 +337,16 @@ async function writeEncryptionKey(opts) {
|
|
|
421
337
|
);
|
|
422
338
|
}
|
|
423
339
|
const target = file === null ? await defaultTarget(opts.cwd) : await fileTarget(file, toError);
|
|
424
|
-
const
|
|
340
|
+
const text = target.text === null ? stringify({
|
|
425
341
|
[ENCRYPTION_KEY_FIELD]: key,
|
|
426
342
|
...typeof previous === "string" ? { [ENCRYPTION_KEY_PREVIOUS_FIELD]: previous } : {}
|
|
427
343
|
}) : withKey(target.text, key, previous, target.source, toError);
|
|
428
|
-
verify(target.text,
|
|
429
|
-
if (opts.dryRun !== true) await writeTextAtomic(target.path,
|
|
344
|
+
verify(target.text, text, key, previous, target.source, toError);
|
|
345
|
+
if (opts.dryRun !== true) await writeTextAtomic(target.path, text);
|
|
430
346
|
return { path: target.path, source: target.source, created: target.text === null };
|
|
431
347
|
}
|
|
432
348
|
function isIgnoredByGit(path) {
|
|
433
|
-
const result = spawnSync("git", ["check-ignore", "-q", "--",
|
|
349
|
+
const result = spawnSync("git", ["check-ignore", "-q", "--", basename3(path)], {
|
|
434
350
|
cwd: dirname2(path),
|
|
435
351
|
stdio: "ignore",
|
|
436
352
|
windowsHide: true
|
|
@@ -491,8 +407,8 @@ function valueAt(data, pointer) {
|
|
|
491
407
|
let node = data;
|
|
492
408
|
for (const seg of segments(pointer)) {
|
|
493
409
|
if (Array.isArray(node)) {
|
|
494
|
-
const
|
|
495
|
-
node = /^\d+$/.test(seg) ?
|
|
410
|
+
const items = node;
|
|
411
|
+
node = /^\d+$/.test(seg) ? items[Number(seg)] : void 0;
|
|
496
412
|
} else if (isRecord(node)) {
|
|
497
413
|
node = Object.hasOwn(node, seg) ? node[seg] : void 0;
|
|
498
414
|
} else {
|
|
@@ -507,10 +423,10 @@ function withValueAt(root, pointer, value) {
|
|
|
507
423
|
const seg = parts[i];
|
|
508
424
|
if (seg === void 0) return value;
|
|
509
425
|
if (Array.isArray(node)) {
|
|
510
|
-
const
|
|
511
|
-
if (!/^\d+$/.test(seg) || Number(seg) >=
|
|
512
|
-
|
|
513
|
-
return
|
|
426
|
+
const items = [...node];
|
|
427
|
+
if (!/^\d+$/.test(seg) || Number(seg) >= items.length) return node;
|
|
428
|
+
items[Number(seg)] = set(items[Number(seg)], i + 1);
|
|
429
|
+
return items;
|
|
514
430
|
}
|
|
515
431
|
if (isRecord(node) && Object.hasOwn(node, seg)) {
|
|
516
432
|
return { ...node, [seg]: set(node[seg], i + 1) };
|
|
@@ -647,6 +563,97 @@ function redactedDerived(f) {
|
|
|
647
563
|
};
|
|
648
564
|
}
|
|
649
565
|
|
|
566
|
+
// src/meta/core/external-metadata.ts
|
|
567
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
568
|
+
import { isAbsolute, relative as relative3, resolve as resolve2, sep } from "node:path";
|
|
569
|
+
import {
|
|
570
|
+
LineCounter,
|
|
571
|
+
isMap as isMap2,
|
|
572
|
+
isNode,
|
|
573
|
+
isScalar,
|
|
574
|
+
isSeq,
|
|
575
|
+
parseDocument as parseDocument2
|
|
576
|
+
} from "yaml";
|
|
577
|
+
|
|
578
|
+
// src/shared/manifest-cas.ts
|
|
579
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
580
|
+
import { posix } from "node:path";
|
|
581
|
+
var COMMIT_ATTEMPTS = 5;
|
|
582
|
+
function conflictRefusal(file) {
|
|
583
|
+
return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
|
|
584
|
+
}
|
|
585
|
+
function heldManifest(path, file, before, absent = false) {
|
|
586
|
+
return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
|
|
587
|
+
}
|
|
588
|
+
function encodeValue(value) {
|
|
589
|
+
return value === void 0 ? "absent" : JSON.stringify(value);
|
|
590
|
+
}
|
|
591
|
+
function baseKey(at2) {
|
|
592
|
+
const entry = at2.join === "path" ? posix.normalize(at2.entry) : at2.entry;
|
|
593
|
+
return `${entry}\0${at2.key}`;
|
|
594
|
+
}
|
|
595
|
+
function rebaseable(held, op, codec) {
|
|
596
|
+
const at2 = baseKey(op);
|
|
597
|
+
if (held.bases.has(at2)) return;
|
|
598
|
+
held.bases.set(at2, {
|
|
599
|
+
entry: op.entry,
|
|
600
|
+
key: op.key,
|
|
601
|
+
join: op.join,
|
|
602
|
+
value: encodeValue(codec.read(held.before, op))
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function rebase(held, current, codec) {
|
|
606
|
+
for (const base of held.bases.values()) {
|
|
607
|
+
if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
|
|
608
|
+
}
|
|
609
|
+
let text = current;
|
|
610
|
+
for (const op of held.ops) text = codec.apply(text, op);
|
|
611
|
+
return text;
|
|
612
|
+
}
|
|
613
|
+
function rebaseline(held) {
|
|
614
|
+
held.before = held.text;
|
|
615
|
+
held.ops = [];
|
|
616
|
+
held.bases.clear();
|
|
617
|
+
held.absent = false;
|
|
618
|
+
}
|
|
619
|
+
async function settle(held, io) {
|
|
620
|
+
for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
|
|
621
|
+
let current;
|
|
622
|
+
try {
|
|
623
|
+
current = await io.read(held.path);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (held.absent && isMissing(error)) {
|
|
626
|
+
await io.write(held.path, held.text);
|
|
627
|
+
held.absent = false;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
631
|
+
throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
|
|
632
|
+
}
|
|
633
|
+
if (!held.absent && current === held.before) {
|
|
634
|
+
await io.write(held.path, held.text);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const replayed = rebase(held, current, io.codec);
|
|
638
|
+
if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
|
|
639
|
+
held.before = current;
|
|
640
|
+
held.text = replayed;
|
|
641
|
+
held.absent = false;
|
|
642
|
+
}
|
|
643
|
+
throw io.toError(conflictRefusal(held.file));
|
|
644
|
+
}
|
|
645
|
+
function isMissing(error) {
|
|
646
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
647
|
+
}
|
|
648
|
+
async function readManifestOrEmpty(absPath, perPage) {
|
|
649
|
+
try {
|
|
650
|
+
return await readFile2(absPath, "utf8");
|
|
651
|
+
} catch (e) {
|
|
652
|
+
if (perPage && isMissing(e)) return "";
|
|
653
|
+
throw e;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
650
657
|
// src/meta/core/external-metadata.ts
|
|
651
658
|
var EXTERNAL_OWNED_SCHEMA = "external:owned";
|
|
652
659
|
var EXTERNAL_DUPLICATE_SCHEMA = "external:duplicate";
|
|
@@ -658,7 +665,7 @@ var perPageRules = /* @__PURE__ */ new WeakMap();
|
|
|
658
665
|
var strayEntries = /* @__PURE__ */ new WeakSet();
|
|
659
666
|
var posix2 = (p) => p.split(sep).join("/");
|
|
660
667
|
function reportedPath(abs, base) {
|
|
661
|
-
const rel =
|
|
668
|
+
const rel = relative3(base, abs);
|
|
662
669
|
return rel === "" ? "." : posix2(rel);
|
|
663
670
|
}
|
|
664
671
|
function reported(of, file) {
|
|
@@ -689,18 +696,18 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
689
696
|
rules.push({ collection: declared, manifest, configDir: opts.configDir });
|
|
690
697
|
continue;
|
|
691
698
|
}
|
|
692
|
-
const
|
|
699
|
+
const join4 = externalMetadataJoin(manifest);
|
|
693
700
|
let parsed;
|
|
694
701
|
let file;
|
|
695
702
|
if (classifyRef(manifest.file).kind === "url") {
|
|
696
703
|
file = manifest.file;
|
|
697
|
-
const
|
|
704
|
+
const text = await fetchExternalMetadata(manifest.file, {
|
|
698
705
|
...manifest.tokenEnv !== void 0 ? { tokenEnv: manifest.tokenEnv } : {},
|
|
699
706
|
...opts.offline !== void 0 ? { offline: opts.offline } : {},
|
|
700
707
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
701
708
|
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
702
709
|
});
|
|
703
|
-
parsed = parseManifest(manifest, collection,
|
|
710
|
+
parsed = parseManifest(manifest, collection, text, file, opts.configDir, join4, ownership(declared, manifest));
|
|
704
711
|
} else {
|
|
705
712
|
const abs = isAbsolute(manifest.file) ? manifest.file : resolve2(opts.configDir, manifest.file);
|
|
706
713
|
const label = reportedPath(abs, opts.base);
|
|
@@ -716,7 +723,7 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
716
723
|
// this run's spelling onto every value and entry instead.
|
|
717
724
|
JSON.stringify([
|
|
718
725
|
collection,
|
|
719
|
-
|
|
726
|
+
join4,
|
|
720
727
|
opts.configDir,
|
|
721
728
|
variantOf(declared, manifest)
|
|
722
729
|
]),
|
|
@@ -726,7 +733,7 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
726
733
|
await readManifest(abs, label),
|
|
727
734
|
label,
|
|
728
735
|
opts.configDir,
|
|
729
|
-
|
|
736
|
+
join4,
|
|
730
737
|
ownership(declared, manifest)
|
|
731
738
|
)
|
|
732
739
|
);
|
|
@@ -740,14 +747,14 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
740
747
|
else owners.set(key, [{ collection, file }]);
|
|
741
748
|
}
|
|
742
749
|
let target;
|
|
743
|
-
if (
|
|
750
|
+
if (join4 === PATH_JOIN) {
|
|
744
751
|
target = byPath;
|
|
745
752
|
} else {
|
|
746
|
-
const existing = byField.get(
|
|
753
|
+
const existing = byField.get(join4);
|
|
747
754
|
if (existing) target = existing;
|
|
748
755
|
else {
|
|
749
756
|
target = /* @__PURE__ */ new Map();
|
|
750
|
-
byField.set(
|
|
757
|
+
byField.set(join4, target);
|
|
751
758
|
}
|
|
752
759
|
}
|
|
753
760
|
for (const [indexKey, values] of parsed.values) {
|
|
@@ -782,14 +789,14 @@ function variantOf(declared, manifest) {
|
|
|
782
789
|
}
|
|
783
790
|
async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
784
791
|
const collection = declared.name;
|
|
785
|
-
const
|
|
792
|
+
const join4 = externalMetadataJoin(manifest);
|
|
786
793
|
const implied = manifest.keys === void 0 ? { collection: declared, manifest, file: manifest.file, files: /* @__PURE__ */ new Set() } : void 0;
|
|
787
794
|
for (const key of manifest.keys ?? []) {
|
|
788
795
|
const list2 = into.owners.get(key);
|
|
789
796
|
if (list2) list2.push({ collection, file: manifest.file });
|
|
790
797
|
else into.owners.set(key, [{ collection, file: manifest.file }]);
|
|
791
798
|
}
|
|
792
|
-
const target = indexFor(
|
|
799
|
+
const target = indexFor(join4, into);
|
|
793
800
|
for (const pageAbs of opts.pages ?? []) {
|
|
794
801
|
const resolved = pageManifestPath(manifest.file, opts.configDir, pageAbs);
|
|
795
802
|
if ("outside" in resolved) {
|
|
@@ -798,7 +805,7 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
798
805
|
}
|
|
799
806
|
continue;
|
|
800
807
|
}
|
|
801
|
-
const rel = posix2(
|
|
808
|
+
const rel = posix2(relative3(opts.configDir, pageAbs));
|
|
802
809
|
if (!isMember(declared, rel)) continue;
|
|
803
810
|
const earlier = resolvedBy.get(resolved.abs);
|
|
804
811
|
if (earlier !== void 0 && earlier !== pageAbs) {
|
|
@@ -811,14 +818,14 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
811
818
|
const label = reportedPath(resolved.abs, opts.base);
|
|
812
819
|
const parsed = await parsedManifests.parse(
|
|
813
820
|
resolved.abs,
|
|
814
|
-
JSON.stringify([collection,
|
|
821
|
+
JSON.stringify([collection, join4, opts.configDir, variantOf(declared, manifest)]),
|
|
815
822
|
async () => parseManifest(
|
|
816
823
|
manifest,
|
|
817
824
|
collection,
|
|
818
825
|
await readPageManifest(resolved.abs, label),
|
|
819
826
|
label,
|
|
820
827
|
opts.configDir,
|
|
821
|
-
|
|
828
|
+
join4,
|
|
822
829
|
ownership(declared, manifest)
|
|
823
830
|
)
|
|
824
831
|
);
|
|
@@ -832,7 +839,7 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
832
839
|
);
|
|
833
840
|
}
|
|
834
841
|
for (const [indexKey, values] of parsed.values) {
|
|
835
|
-
const key =
|
|
842
|
+
const key = join4 === PATH_JOIN ? pageAbs : indexKey;
|
|
836
843
|
const merged = target.get(key) ?? /* @__PURE__ */ new Map();
|
|
837
844
|
for (const [owned, value] of values) merged.set(owned, reported(value, label));
|
|
838
845
|
target.set(key, merged);
|
|
@@ -846,12 +853,12 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
846
853
|
}
|
|
847
854
|
return implied;
|
|
848
855
|
}
|
|
849
|
-
function indexFor(
|
|
850
|
-
if (
|
|
851
|
-
const existing = into.byField.get(
|
|
856
|
+
function indexFor(join4, into) {
|
|
857
|
+
if (join4 === PATH_JOIN) return into.byPath;
|
|
858
|
+
const existing = into.byField.get(join4);
|
|
852
859
|
if (existing) return existing;
|
|
853
860
|
const created = /* @__PURE__ */ new Map();
|
|
854
|
-
into.byField.set(
|
|
861
|
+
into.byField.set(join4, created);
|
|
855
862
|
return created;
|
|
856
863
|
}
|
|
857
864
|
function outsideRefusal(manifest, collection, pageRel) {
|
|
@@ -881,11 +888,11 @@ function unreadable(file, err) {
|
|
|
881
888
|
const reason = err instanceof Error ? err.message : String(err);
|
|
882
889
|
return new DocmetaError(`Manifest ${file} could not be read: ${reason}`);
|
|
883
890
|
}
|
|
884
|
-
function parseManifest(manifest, collection,
|
|
891
|
+
function parseManifest(manifest, collection, text, file, configDir, join4, owns) {
|
|
885
892
|
const target = /* @__PURE__ */ new Map();
|
|
886
893
|
const entries = [];
|
|
887
894
|
const lc = new LineCounter();
|
|
888
|
-
const doc = parseDocument2(
|
|
895
|
+
const doc = parseDocument2(text, { lineCounter: lc, uniqueKeys: false });
|
|
889
896
|
const problem = doc.errors[0];
|
|
890
897
|
if (problem) {
|
|
891
898
|
throw new DocmetaError(
|
|
@@ -897,7 +904,7 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
|
|
|
897
904
|
return { values: target, entries };
|
|
898
905
|
if (!isMap2(root)) {
|
|
899
906
|
throw new DocmetaError(
|
|
900
|
-
`Manifest ${file}: the manifest must be a mapping from document ${
|
|
907
|
+
`Manifest ${file}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
|
|
901
908
|
);
|
|
902
909
|
}
|
|
903
910
|
const lineAt = (node) => {
|
|
@@ -921,7 +928,7 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
|
|
|
921
928
|
`Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
|
|
922
929
|
);
|
|
923
930
|
}
|
|
924
|
-
const indexKey =
|
|
931
|
+
const indexKey = join4 === PATH_JOIN ? resolve2(configDir, spelled) : spelled;
|
|
925
932
|
const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
|
|
926
933
|
for (const kv of pair.value.items) {
|
|
927
934
|
const key = isScalar(kv.key) ? String(kv.key.value) : String(kv.key);
|
|
@@ -947,8 +954,8 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
|
|
|
947
954
|
target.set(indexKey, values);
|
|
948
955
|
entries.push({
|
|
949
956
|
collection,
|
|
950
|
-
join:
|
|
951
|
-
...
|
|
957
|
+
join: join4,
|
|
958
|
+
...join4 === PATH_JOIN ? { abs: indexKey } : {},
|
|
952
959
|
spelled,
|
|
953
960
|
file,
|
|
954
961
|
...entryLine === void 0 ? {} : { line: entryLine }
|
|
@@ -1209,6 +1216,9 @@ function isFieldLocation(value) {
|
|
|
1209
1216
|
return value === "page" || value === "external";
|
|
1210
1217
|
}
|
|
1211
1218
|
|
|
1219
|
+
// src/meta/core/graph-output.ts
|
|
1220
|
+
var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
|
|
1221
|
+
|
|
1212
1222
|
// src/meta/core/validator.ts
|
|
1213
1223
|
import * as AjvDraft07Ns from "ajv";
|
|
1214
1224
|
import * as Ajv2019Ns from "ajv/dist/2019.js";
|
|
@@ -1216,11 +1226,6 @@ import * as Ajv2020Ns from "ajv/dist/2020.js";
|
|
|
1216
1226
|
import * as AjvDraft04Ns from "ajv-draft-04";
|
|
1217
1227
|
import * as addFormatsNs from "ajv-formats";
|
|
1218
1228
|
import { createRequire } from "node:module";
|
|
1219
|
-
|
|
1220
|
-
// src/meta/core/graph-output.ts
|
|
1221
|
-
var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
|
|
1222
|
-
|
|
1223
|
-
// src/meta/core/validator.ts
|
|
1224
1229
|
var draft06MetaSchema = createRequire(import.meta.url)(
|
|
1225
1230
|
"ajv/dist/refs/json-schema-draft-06.json"
|
|
1226
1231
|
);
|
|
@@ -1835,23 +1840,6 @@ function familyMarks(opts) {
|
|
|
1835
1840
|
})();
|
|
1836
1841
|
}
|
|
1837
1842
|
|
|
1838
|
-
// src/meta/core/relocation.ts
|
|
1839
|
-
import { access, readFile as readFile4, stat as stat2, unlink } from "node:fs/promises";
|
|
1840
|
-
import { basename as basename3, extname, isAbsolute as isAbsolute3, join as join3, relative as relative4, resolve as resolve3, sep as sep3 } from "node:path";
|
|
1841
|
-
import picomatch from "picomatch";
|
|
1842
|
-
import {
|
|
1843
|
-
LineCounter as LineCounter3,
|
|
1844
|
-
isMap as isMap4,
|
|
1845
|
-
isNode as isNode3,
|
|
1846
|
-
isScalar as isScalar3,
|
|
1847
|
-
isSeq as isSeq2,
|
|
1848
|
-
parse as parseYaml2,
|
|
1849
|
-
parseDocument as parseDocument4
|
|
1850
|
-
} from "yaml";
|
|
1851
|
-
|
|
1852
|
-
// src/meta/core/collections.ts
|
|
1853
|
-
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolvePath, sep as sep2 } from "node:path";
|
|
1854
|
-
|
|
1855
1843
|
// src/meta/core/projection.ts
|
|
1856
1844
|
var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
|
|
1857
1845
|
var RESERVED = new Set(SYSTEM_COLUMNS);
|
|
@@ -1982,7 +1970,7 @@ function isParamName(name) {
|
|
|
1982
1970
|
return PARAM_NAME_HEAD.exec(name)?.[0] === name;
|
|
1983
1971
|
}
|
|
1984
1972
|
function collectNamedParameters(sql) {
|
|
1985
|
-
const
|
|
1973
|
+
const tokens = [];
|
|
1986
1974
|
const seen = /* @__PURE__ */ new Set();
|
|
1987
1975
|
const prefixOf = /* @__PURE__ */ new Map();
|
|
1988
1976
|
let i = 0;
|
|
@@ -2012,14 +2000,14 @@ function collectNamedParameters(sql) {
|
|
|
2012
2000
|
prefixOf.set(m[0], ch);
|
|
2013
2001
|
if (!seen.has(token)) {
|
|
2014
2002
|
seen.add(token);
|
|
2015
|
-
|
|
2003
|
+
tokens.push(token);
|
|
2016
2004
|
}
|
|
2017
2005
|
i += 1 + m[0].length;
|
|
2018
2006
|
} else {
|
|
2019
2007
|
i++;
|
|
2020
2008
|
}
|
|
2021
2009
|
}
|
|
2022
|
-
return
|
|
2010
|
+
return tokens;
|
|
2023
2011
|
}
|
|
2024
2012
|
function stripLeadingTrivia(sql) {
|
|
2025
2013
|
let i = 0;
|
|
@@ -2032,16 +2020,16 @@ function stripLeadingTrivia(sql) {
|
|
|
2032
2020
|
return sql.slice(i);
|
|
2033
2021
|
}
|
|
2034
2022
|
}
|
|
2035
|
-
function isTrivia(
|
|
2023
|
+
function isTrivia(text) {
|
|
2036
2024
|
let i = 0;
|
|
2037
|
-
while (i <
|
|
2038
|
-
const ch =
|
|
2025
|
+
while (i < text.length) {
|
|
2026
|
+
const ch = text[i];
|
|
2039
2027
|
if (ch === void 0 || /\s/.test(ch) || ch === ";") {
|
|
2040
2028
|
i++;
|
|
2041
2029
|
continue;
|
|
2042
2030
|
}
|
|
2043
|
-
if (startsComment(
|
|
2044
|
-
i = skipComment(
|
|
2031
|
+
if (startsComment(text, i)) {
|
|
2032
|
+
i = skipComment(text, i);
|
|
2045
2033
|
continue;
|
|
2046
2034
|
}
|
|
2047
2035
|
return false;
|
|
@@ -2053,8 +2041,8 @@ function loadSqlite() {
|
|
|
2053
2041
|
sqliteModule ??= (async () => {
|
|
2054
2042
|
const original = process.emitWarning.bind(process);
|
|
2055
2043
|
const filtered = (warning, ...rest) => {
|
|
2056
|
-
const
|
|
2057
|
-
if (
|
|
2044
|
+
const text = typeof warning === "string" ? warning : warning.message;
|
|
2045
|
+
if (text.includes("SQLite is an experimental feature")) return;
|
|
2058
2046
|
Reflect.apply(original, process, [warning, ...rest]);
|
|
2059
2047
|
};
|
|
2060
2048
|
process.emitWarning = filtered;
|
|
@@ -2068,10 +2056,11 @@ function loadSqlite() {
|
|
|
2068
2056
|
}
|
|
2069
2057
|
|
|
2070
2058
|
// src/meta/core/collections.ts
|
|
2059
|
+
import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolvePath, sep as sep2 } from "node:path";
|
|
2071
2060
|
function memberOf(collections, configDir, base, label) {
|
|
2072
2061
|
if (collections.length === 0) return [];
|
|
2073
2062
|
if (label === STDIN_LABEL) return [];
|
|
2074
|
-
const rel =
|
|
2063
|
+
const rel = relative4(configDir, resolvePath(base, label)).split(sep2).join("/");
|
|
2075
2064
|
if (isAbsolute2(rel)) return [];
|
|
2076
2065
|
return collections.filter((c) => isMember(c, rel)).map((c) => c.name);
|
|
2077
2066
|
}
|
|
@@ -2108,6 +2097,20 @@ function createCollectionViews(db, collections) {
|
|
|
2108
2097
|
}
|
|
2109
2098
|
}
|
|
2110
2099
|
|
|
2100
|
+
// src/meta/core/relocation.ts
|
|
2101
|
+
import { access, readFile as readFile4, stat as stat2, unlink } from "node:fs/promises";
|
|
2102
|
+
import { basename as basename4, extname, isAbsolute as isAbsolute3, join as join3, relative as relative5, resolve as resolve3, sep as sep3 } from "node:path";
|
|
2103
|
+
import picomatch from "picomatch";
|
|
2104
|
+
import {
|
|
2105
|
+
LineCounter as LineCounter3,
|
|
2106
|
+
isMap as isMap4,
|
|
2107
|
+
isNode as isNode3,
|
|
2108
|
+
isScalar as isScalar3,
|
|
2109
|
+
isSeq as isSeq2,
|
|
2110
|
+
parse as parseYaml2,
|
|
2111
|
+
parseDocument as parseDocument4
|
|
2112
|
+
} from "yaml";
|
|
2113
|
+
|
|
2111
2114
|
// src/meta/core/external-metadata-write.ts
|
|
2112
2115
|
import { posix as posix3 } from "node:path";
|
|
2113
2116
|
import {
|
|
@@ -2118,9 +2121,9 @@ import {
|
|
|
2118
2121
|
parseDocument as parseDocument3,
|
|
2119
2122
|
stringify as stringify2
|
|
2120
2123
|
} from "yaml";
|
|
2121
|
-
function spliceManifestValue(
|
|
2124
|
+
function spliceManifestValue(text, options) {
|
|
2122
2125
|
const { entry, key, value, file } = options;
|
|
2123
|
-
const
|
|
2126
|
+
const join4 = options.join ?? PATH_JOIN;
|
|
2124
2127
|
const at2 = (line2) => {
|
|
2125
2128
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2126
2129
|
if (line2 === void 0) return name;
|
|
@@ -2134,9 +2137,9 @@ function spliceManifestValue(text2, options) {
|
|
|
2134
2137
|
if (value === void 0) {
|
|
2135
2138
|
throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
|
|
2136
2139
|
}
|
|
2137
|
-
const eol = detectEol(
|
|
2140
|
+
const eol = detectEol(text);
|
|
2138
2141
|
const lc = new LineCounter2();
|
|
2139
|
-
const doc = parseDocument3(
|
|
2142
|
+
const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
|
|
2140
2143
|
const problem = doc.errors[0];
|
|
2141
2144
|
if (problem) {
|
|
2142
2145
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2145,18 +2148,18 @@ function spliceManifestValue(text2, options) {
|
|
|
2145
2148
|
const r = rangeOf(node);
|
|
2146
2149
|
return r ? lc.linePos(r[0]).line : void 0;
|
|
2147
2150
|
};
|
|
2148
|
-
const same =
|
|
2151
|
+
const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2149
2152
|
const root = doc.contents;
|
|
2150
2153
|
let spelled = entry;
|
|
2151
2154
|
let edit;
|
|
2152
|
-
if (root === null || isScalar2(root) && root.value === null && emptySource(
|
|
2153
|
-
edit = startManifest(
|
|
2155
|
+
if (root === null || isScalar2(root) && root.value === null && emptySource(text, root)) {
|
|
2156
|
+
edit = startManifest(text, entry, key, value, eol);
|
|
2154
2157
|
} else if (!isMap3(root)) {
|
|
2155
2158
|
throw new DocmetaError(
|
|
2156
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2159
|
+
`${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
|
|
2157
2160
|
);
|
|
2158
2161
|
} else {
|
|
2159
|
-
const style = detectStyle(
|
|
2162
|
+
const style = detectStyle(text, root, eol);
|
|
2160
2163
|
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2161
2164
|
const [first, second] = matches;
|
|
2162
2165
|
if (first && second) {
|
|
@@ -2170,7 +2173,7 @@ function spliceManifestValue(text2, options) {
|
|
|
2170
2173
|
`${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
|
|
2171
2174
|
);
|
|
2172
2175
|
}
|
|
2173
|
-
edit = appendEntry(
|
|
2176
|
+
edit = appendEntry(text, root, entry, key, value, style);
|
|
2174
2177
|
} else {
|
|
2175
2178
|
spelled = keyString(first.key);
|
|
2176
2179
|
const where = at2(keyLine(first.key));
|
|
@@ -2193,18 +2196,18 @@ function spliceManifestValue(text2, options) {
|
|
|
2193
2196
|
);
|
|
2194
2197
|
let made;
|
|
2195
2198
|
if (v.flow === true) {
|
|
2196
|
-
if (hit) made = replaceFlowValue(
|
|
2197
|
-
else if (v.items.length === 0) made = fillEmptyEntry(
|
|
2199
|
+
if (hit) made = replaceFlowValue(text, hit, value);
|
|
2200
|
+
else if (v.items.length === 0) made = fillEmptyEntry(text, first, key, value, style);
|
|
2198
2201
|
else throw flowRefusal();
|
|
2199
2202
|
if (!made) throw hit ? flowRefusal() : formRefusal();
|
|
2200
2203
|
} else {
|
|
2201
|
-
made = hit ? replaceBlockValue(
|
|
2204
|
+
made = hit ? replaceBlockValue(text, hit, value, style) : insertKey(text, v, key, value, style);
|
|
2202
2205
|
if (!made) throw formRefusal();
|
|
2203
2206
|
}
|
|
2204
2207
|
edit = made;
|
|
2205
2208
|
}
|
|
2206
2209
|
}
|
|
2207
|
-
const out =
|
|
2210
|
+
const out = text.slice(0, edit.start) + edit.insert + text.slice(edit.end);
|
|
2208
2211
|
const line = readBack(
|
|
2209
2212
|
doc,
|
|
2210
2213
|
out,
|
|
@@ -2217,13 +2220,13 @@ function spliceManifestValue(text2, options) {
|
|
|
2217
2220
|
);
|
|
2218
2221
|
return { text: out, line };
|
|
2219
2222
|
}
|
|
2220
|
-
function readManifestValue(
|
|
2221
|
-
const
|
|
2222
|
-
const doc = parseDocument3(
|
|
2223
|
+
function readManifestValue(text, options) {
|
|
2224
|
+
const join4 = options.join ?? PATH_JOIN;
|
|
2225
|
+
const doc = parseDocument3(text, { uniqueKeys: false });
|
|
2223
2226
|
if (doc.errors.length > 0) return void 0;
|
|
2224
2227
|
const root = doc.contents;
|
|
2225
2228
|
if (!isMap3(root)) return void 0;
|
|
2226
|
-
const same =
|
|
2229
|
+
const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2227
2230
|
const pair = root.items.find((p) => same(keyString(p.key), options.entry));
|
|
2228
2231
|
const held = pair?.value;
|
|
2229
2232
|
if (!isMap3(held)) return void 0;
|
|
@@ -2231,17 +2234,17 @@ function readManifestValue(text2, options) {
|
|
|
2231
2234
|
const node = kv?.value;
|
|
2232
2235
|
return isNode2(node) ? node.toJSON() : void 0;
|
|
2233
2236
|
}
|
|
2234
|
-
function removeManifestKey(
|
|
2237
|
+
function removeManifestKey(text, options) {
|
|
2235
2238
|
const { entry, key, file } = options;
|
|
2236
|
-
const
|
|
2239
|
+
const join4 = options.join ?? PATH_JOIN;
|
|
2237
2240
|
const at2 = (line) => {
|
|
2238
2241
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2239
2242
|
if (line === void 0) return name;
|
|
2240
2243
|
return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
|
|
2241
2244
|
};
|
|
2242
|
-
const eol = detectEol(
|
|
2245
|
+
const eol = detectEol(text);
|
|
2243
2246
|
const lc = new LineCounter2();
|
|
2244
|
-
const doc = parseDocument3(
|
|
2247
|
+
const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
|
|
2245
2248
|
const problem = doc.errors[0];
|
|
2246
2249
|
if (problem) {
|
|
2247
2250
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2251,13 +2254,13 @@ function removeManifestKey(text2, options) {
|
|
|
2251
2254
|
return r ? lc.linePos(r[0]).line : void 0;
|
|
2252
2255
|
};
|
|
2253
2256
|
const root = doc.contents;
|
|
2254
|
-
if (root === null || isScalar2(root) && root.value === null) return { text
|
|
2257
|
+
if (root === null || isScalar2(root) && root.value === null) return { text };
|
|
2255
2258
|
if (!isMap3(root)) {
|
|
2256
2259
|
throw new DocmetaError(
|
|
2257
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2260
|
+
`${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
|
|
2258
2261
|
);
|
|
2259
2262
|
}
|
|
2260
|
-
const same =
|
|
2263
|
+
const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2261
2264
|
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2262
2265
|
const [first, second] = matches;
|
|
2263
2266
|
if (first && second) {
|
|
@@ -2265,7 +2268,7 @@ function removeManifestKey(text2, options) {
|
|
|
2265
2268
|
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
2266
2269
|
);
|
|
2267
2270
|
}
|
|
2268
|
-
if (first === void 0) return { text
|
|
2271
|
+
if (first === void 0) return { text };
|
|
2269
2272
|
const spelled = keyString(first.key);
|
|
2270
2273
|
const where = at2(keyLine(first.key));
|
|
2271
2274
|
const v = first.value;
|
|
@@ -2279,7 +2282,7 @@ function removeManifestKey(text2, options) {
|
|
|
2279
2282
|
`${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
|
|
2280
2283
|
);
|
|
2281
2284
|
}
|
|
2282
|
-
if (hit === void 0) return { text
|
|
2285
|
+
if (hit === void 0) return { text };
|
|
2283
2286
|
const whole = v.items.length === 1;
|
|
2284
2287
|
if (!whole && v.flow === true) {
|
|
2285
2288
|
throw new DocmetaError(
|
|
@@ -2287,18 +2290,18 @@ function removeManifestKey(text2, options) {
|
|
|
2287
2290
|
);
|
|
2288
2291
|
}
|
|
2289
2292
|
const from = rangeOf(whole ? first.key : hit.key);
|
|
2290
|
-
const to = whole ? contentEnd(
|
|
2293
|
+
const to = whole ? contentEnd(text, v) : contentEnd(text, hit.value) ?? contentEnd(text, hit.key);
|
|
2291
2294
|
if (!from || to === void 0) {
|
|
2292
2295
|
throw new DocmetaError(
|
|
2293
2296
|
`${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
|
|
2294
2297
|
);
|
|
2295
2298
|
}
|
|
2296
|
-
let { start, end } = keyLines(
|
|
2299
|
+
let { start, end } = keyLines(text, from[0], to, eol);
|
|
2297
2300
|
if (whole) {
|
|
2298
|
-
if (
|
|
2299
|
-
else if (start === 0 &&
|
|
2301
|
+
if (text.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
|
|
2302
|
+
else if (start === 0 && text.startsWith(eol, end)) end += eol.length;
|
|
2300
2303
|
}
|
|
2301
|
-
const out =
|
|
2304
|
+
const out = text.slice(0, start) + text.slice(end);
|
|
2302
2305
|
const after = parseDocument3(out, { uniqueKeys: false });
|
|
2303
2306
|
const fail = (why) => new DocmetaError(
|
|
2304
2307
|
`${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
|
|
@@ -2317,16 +2320,16 @@ function removeManifestKey(text2, options) {
|
|
|
2317
2320
|
if (!deepEqual(now ?? null, expected)) throw fail("Another value in the manifest would have changed.");
|
|
2318
2321
|
return { text: out };
|
|
2319
2322
|
}
|
|
2320
|
-
function renameManifestEntry(
|
|
2323
|
+
function renameManifestEntry(text, options) {
|
|
2321
2324
|
const { entry, to, file } = options;
|
|
2322
|
-
const
|
|
2325
|
+
const join4 = options.join ?? PATH_JOIN;
|
|
2323
2326
|
const at2 = (line) => {
|
|
2324
2327
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2325
2328
|
if (line === void 0) return name;
|
|
2326
2329
|
return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
|
|
2327
2330
|
};
|
|
2328
2331
|
const lc = new LineCounter2();
|
|
2329
|
-
const doc = parseDocument3(
|
|
2332
|
+
const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
|
|
2330
2333
|
const problem = doc.errors[0];
|
|
2331
2334
|
if (problem) {
|
|
2332
2335
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2336,20 +2339,20 @@ function renameManifestEntry(text2, options) {
|
|
|
2336
2339
|
return r2 ? lc.linePos(r2[0]).line : void 0;
|
|
2337
2340
|
};
|
|
2338
2341
|
const root = doc.contents;
|
|
2339
|
-
if (root === null || isScalar2(root) && root.value === null) return { text
|
|
2342
|
+
if (root === null || isScalar2(root) && root.value === null) return { text };
|
|
2340
2343
|
if (!isMap3(root)) {
|
|
2341
2344
|
throw new DocmetaError(
|
|
2342
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2345
|
+
`${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
|
|
2343
2346
|
);
|
|
2344
2347
|
}
|
|
2345
|
-
const same =
|
|
2348
|
+
const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2346
2349
|
const [first, second] = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2347
2350
|
if (first && second) {
|
|
2348
2351
|
throw new DocmetaError(
|
|
2349
2352
|
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
2350
2353
|
);
|
|
2351
2354
|
}
|
|
2352
|
-
if (first === void 0) return { text
|
|
2355
|
+
if (first === void 0) return { text };
|
|
2353
2356
|
const taken = root.items.find((p) => same(keyString(p.key), to));
|
|
2354
2357
|
if (taken !== void 0) {
|
|
2355
2358
|
throw new DocmetaError(
|
|
@@ -2358,8 +2361,8 @@ function renameManifestEntry(text2, options) {
|
|
|
2358
2361
|
}
|
|
2359
2362
|
const spelled = keyString(first.key);
|
|
2360
2363
|
const r = rangeOf(first.key);
|
|
2361
|
-
const end = contentEnd(
|
|
2362
|
-
if (!r || end === void 0 || colonEnd(
|
|
2364
|
+
const end = contentEnd(text, first.key);
|
|
2365
|
+
if (!r || end === void 0 || colonEnd(text, first) === void 0) {
|
|
2363
2366
|
throw new DocmetaError(
|
|
2364
2367
|
`${at2(keyLine(first.key))}: "${spelled}" is written in a form this writer does not edit. Write it as "${spelled}: \u2026".`
|
|
2365
2368
|
);
|
|
@@ -2367,7 +2370,7 @@ function renameManifestEntry(text2, options) {
|
|
|
2367
2370
|
const quoted = isScalar2(first.key) && (first.key.type === "QUOTE_DOUBLE" || first.key.type === "QUOTE_SINGLE");
|
|
2368
2371
|
const plain = stringify2(to, { lineWidth: 0 }).replace(/\n$/, "");
|
|
2369
2372
|
const insert = quoted || plain.includes("\n") ? JSON.stringify(to) : plain;
|
|
2370
|
-
const out =
|
|
2373
|
+
const out = text.slice(0, r[0]) + insert + text.slice(end);
|
|
2371
2374
|
const after = parseDocument3(out, { uniqueKeys: false });
|
|
2372
2375
|
const fail = (why) => new DocmetaError(
|
|
2373
2376
|
`${at2()}: renaming "${spelled}" to "${to}" did not read back as renamed, so nothing was written. ${why}`
|
|
@@ -2382,25 +2385,25 @@ function renameManifestEntry(text2, options) {
|
|
|
2382
2385
|
if (!deepEqual(now, expected)) throw fail("Another value in the manifest would have changed.");
|
|
2383
2386
|
return { text: out };
|
|
2384
2387
|
}
|
|
2385
|
-
function startManifest(
|
|
2388
|
+
function startManifest(text, entry, key, value, eol) {
|
|
2386
2389
|
const style = { step: 2, indentSeq: true, eol };
|
|
2387
2390
|
const block = blockLines({ [entry]: { [key]: value } }, 0, style);
|
|
2388
2391
|
let insert;
|
|
2389
|
-
if (
|
|
2392
|
+
if (text === "" || text.endsWith("\n")) insert = block + eol;
|
|
2390
2393
|
else insert = eol + block;
|
|
2391
|
-
return { start:
|
|
2394
|
+
return { start: text.length, end: text.length, insert };
|
|
2392
2395
|
}
|
|
2393
|
-
function appendEntry(
|
|
2396
|
+
function appendEntry(text, root, entry, key, value, style) {
|
|
2394
2397
|
const body = { [entry]: { [key]: value } };
|
|
2395
2398
|
if (root.flow === true) {
|
|
2396
2399
|
const r = rangeOf(root);
|
|
2397
|
-
const start = r ? r[0] :
|
|
2398
|
-
const end = contentEnd(
|
|
2399
|
-
return { start, end, insert: blockLines(body, colOf(
|
|
2400
|
+
const start = r ? r[0] : text.length;
|
|
2401
|
+
const end = contentEnd(text, root) ?? start;
|
|
2402
|
+
return { start, end, insert: blockLines(body, colOf(text, start), style) };
|
|
2400
2403
|
}
|
|
2401
2404
|
const firstKey = rangeOf(root.items[0]?.key);
|
|
2402
|
-
const col = firstKey ? colOf(
|
|
2403
|
-
const pos = lineEnd(
|
|
2405
|
+
const col = firstKey ? colOf(text, firstKey[0]) : 0;
|
|
2406
|
+
const pos = lineEnd(text, contentEnd(text, root) ?? text.length);
|
|
2404
2407
|
const last = root.items.at(-1);
|
|
2405
2408
|
const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
|
|
2406
2409
|
return {
|
|
@@ -2409,12 +2412,12 @@ function appendEntry(text2, root, entry, key, value, style) {
|
|
|
2409
2412
|
insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
|
|
2410
2413
|
};
|
|
2411
2414
|
}
|
|
2412
|
-
function fillEmptyEntry(
|
|
2413
|
-
const start = colonEnd(
|
|
2415
|
+
function fillEmptyEntry(text, entryPair, key, value, style) {
|
|
2416
|
+
const start = colonEnd(text, entryPair);
|
|
2414
2417
|
const keyRange = rangeOf(entryPair.key);
|
|
2415
|
-
const end = contentEnd(
|
|
2418
|
+
const end = contentEnd(text, entryPair.value);
|
|
2416
2419
|
if (start === void 0 || !keyRange || end === void 0) return void 0;
|
|
2417
|
-
const col = colOf(
|
|
2420
|
+
const col = colOf(text, keyRange[0]) + style.step;
|
|
2418
2421
|
return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
|
|
2419
2422
|
}
|
|
2420
2423
|
function readBack(before, out, spelled, key, value, fail) {
|
|
@@ -2455,17 +2458,17 @@ function isRecord3(x) {
|
|
|
2455
2458
|
function relocateFailed(result) {
|
|
2456
2459
|
return result.summary.stayed > 0;
|
|
2457
2460
|
}
|
|
2458
|
-
function relocationContext(
|
|
2461
|
+
function relocationContext(run, extras) {
|
|
2459
2462
|
return {
|
|
2460
2463
|
cwd: extras.cwd,
|
|
2461
|
-
base:
|
|
2464
|
+
base: run.base,
|
|
2462
2465
|
noConfig: extras.noConfig ?? false,
|
|
2463
|
-
config:
|
|
2464
|
-
...
|
|
2465
|
-
...
|
|
2466
|
-
...
|
|
2467
|
-
collections:
|
|
2468
|
-
declaredCollections:
|
|
2466
|
+
config: run.config,
|
|
2467
|
+
...run.configDir !== void 0 ? { configDir: run.configDir } : {},
|
|
2468
|
+
...run.configPath !== void 0 ? { configPath: run.configPath } : {},
|
|
2469
|
+
...run.configFile !== void 0 ? { configFile: run.configFile } : {},
|
|
2470
|
+
collections: run.collections,
|
|
2471
|
+
declaredCollections: run.declaredCollections,
|
|
2469
2472
|
targets: extras.targets.filter((t) => t !== STDIN_TOKEN),
|
|
2470
2473
|
...extras.cliSchemas !== void 0 ? { cliSchemas: extras.cliSchemas } : {},
|
|
2471
2474
|
validator: extras.validator,
|
|
@@ -2477,11 +2480,11 @@ function relocationContext(run2, extras) {
|
|
|
2477
2480
|
}
|
|
2478
2481
|
var toPosix = (p) => p.split(sep3).join("/");
|
|
2479
2482
|
function reported2(abs, base) {
|
|
2480
|
-
const rel =
|
|
2483
|
+
const rel = relative5(base, abs);
|
|
2481
2484
|
return rel === "" ? "." : toPosix(rel);
|
|
2482
2485
|
}
|
|
2483
2486
|
function configSource(path, cwd) {
|
|
2484
|
-
return toPosix(
|
|
2487
|
+
return toPosix(relative5(cwd, path)) || basename4(path);
|
|
2485
2488
|
}
|
|
2486
2489
|
function cloneCollection(c) {
|
|
2487
2490
|
return {
|
|
@@ -2692,7 +2695,7 @@ var Model = class {
|
|
|
2692
2695
|
/** A label relative to the config directory, posix; undefined outside it. */
|
|
2693
2696
|
relative(label) {
|
|
2694
2697
|
if (label === "<stdin>") return void 0;
|
|
2695
|
-
const rel = toPosix(
|
|
2698
|
+
const rel = toPosix(relative5(this.configDir, resolve3(this.ctx.base, label)));
|
|
2696
2699
|
return isAbsolute3(rel) || rel.startsWith("../") ? void 0 : rel;
|
|
2697
2700
|
}
|
|
2698
2701
|
};
|
|
@@ -2704,17 +2707,17 @@ function bare(path) {
|
|
|
2704
2707
|
}
|
|
2705
2708
|
async function collectionPath(target, cwd, configDir) {
|
|
2706
2709
|
if (target === STDIN_TOKEN) return void 0;
|
|
2707
|
-
const
|
|
2708
|
-
const scanned = picomatch.scan(
|
|
2710
|
+
const posix4 = target.replace(/\\/g, "/");
|
|
2711
|
+
const scanned = picomatch.scan(posix4);
|
|
2709
2712
|
if (scanned.isGlob) {
|
|
2710
|
-
const rel2 = toPosix(
|
|
2713
|
+
const rel2 = toPosix(relative5(configDir, resolve3(cwd, scanned.base === "" ? "." : scanned.base)));
|
|
2711
2714
|
if (isAbsolute3(rel2) || rel2 === ".." || rel2.startsWith("../")) return void 0;
|
|
2712
2715
|
return rel2 === "" ? scanned.glob : `${rel2}/${scanned.glob}`;
|
|
2713
2716
|
}
|
|
2714
|
-
const abs = resolve3(cwd,
|
|
2715
|
-
const rel = toPosix(
|
|
2717
|
+
const abs = resolve3(cwd, posix4);
|
|
2718
|
+
const rel = toPosix(relative5(configDir, abs));
|
|
2716
2719
|
if (isAbsolute3(rel) || rel.startsWith("..")) return void 0;
|
|
2717
|
-
let directory =
|
|
2720
|
+
let directory = posix4.endsWith("/");
|
|
2718
2721
|
if (!directory) {
|
|
2719
2722
|
try {
|
|
2720
2723
|
directory = (await stat2(abs)).isDirectory();
|
|
@@ -2773,7 +2776,7 @@ function marksOf(ctx) {
|
|
|
2773
2776
|
});
|
|
2774
2777
|
}
|
|
2775
2778
|
function entryFor(m, label, data, configDir, base, key) {
|
|
2776
|
-
if (m.join === PATH_JOIN) return toPosix(
|
|
2779
|
+
if (m.join === PATH_JOIN) return toPosix(relative5(configDir, resolve3(base, label)));
|
|
2777
2780
|
let raw = data[m.join];
|
|
2778
2781
|
if (isEncryptedValue(raw)) {
|
|
2779
2782
|
const k = key();
|
|
@@ -3089,9 +3092,9 @@ async function planRelocation(ctx, request) {
|
|
|
3089
3092
|
}
|
|
3090
3093
|
const orderedPages = () => {
|
|
3091
3094
|
const all = [...pages.values()];
|
|
3092
|
-
const
|
|
3095
|
+
const run = all.filter((p) => !p.beyond).sort((a, b) => compare(a.label, b.label));
|
|
3093
3096
|
const beyond = all.filter((p) => p.beyond).sort((a, b) => compare(a.label, b.label));
|
|
3094
|
-
return [...
|
|
3097
|
+
return [...run, ...beyond];
|
|
3095
3098
|
};
|
|
3096
3099
|
let dirty = new Set(pages.values());
|
|
3097
3100
|
while (dirty.size > 0) {
|
|
@@ -3153,20 +3156,20 @@ async function planRelocation(ctx, request) {
|
|
|
3153
3156
|
for (const [path, { file, outs, ins }] of files) {
|
|
3154
3157
|
if (!m.created && outs.length === 0 && ins.length === 0) continue;
|
|
3155
3158
|
const disk = m.created ? null : await readManifestText(path, file, m.perPage);
|
|
3156
|
-
let
|
|
3157
|
-
const before =
|
|
3159
|
+
let text = disk ?? "";
|
|
3160
|
+
const before = text;
|
|
3158
3161
|
const where = { join: m.join, file };
|
|
3159
3162
|
for (const o of outs) {
|
|
3160
|
-
if (!o.drop)
|
|
3163
|
+
if (!o.drop) text = spliceManifestValue(text, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
|
|
3161
3164
|
}
|
|
3162
|
-
for (const i of ins)
|
|
3163
|
-
const found = keyLines2(
|
|
3165
|
+
for (const i of ins) text = removeManifestKey(text, { ...where, entry: i.entry, key: i.key }).text;
|
|
3166
|
+
const found = keyLines2(text, m.join);
|
|
3164
3167
|
for (const o of outs) {
|
|
3165
3168
|
const line = found.get(entryId(o.entry, m.join))?.get(o.key);
|
|
3166
3169
|
if (line !== void 0) lines.set(lineId(o.page, file, o.key), line);
|
|
3167
3170
|
}
|
|
3168
|
-
if (m.created ||
|
|
3169
|
-
writes.push({ path, text
|
|
3171
|
+
if (m.created || text !== before) {
|
|
3172
|
+
writes.push({ path, text, kind: "manifest", before: disk });
|
|
3170
3173
|
}
|
|
3171
3174
|
}
|
|
3172
3175
|
}
|
|
@@ -3180,9 +3183,9 @@ async function planRelocation(ctx, request) {
|
|
|
3180
3183
|
`${ctx.configFile.source} is a single-tool config file, which cannot hold collections:. Move its keys under meta: in ${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}.`
|
|
3181
3184
|
);
|
|
3182
3185
|
}
|
|
3183
|
-
const
|
|
3184
|
-
verifyConfig(target.text,
|
|
3185
|
-
writes.push({ path: target.path, text
|
|
3186
|
+
const text = editConfig(target.text, model, configSource(target.path, ctx.cwd));
|
|
3187
|
+
verifyConfig(target.text, text, finalCollections, configSource(target.path, ctx.cwd));
|
|
3188
|
+
writes.push({ path: target.path, text, kind: "config", before: target.disk });
|
|
3186
3189
|
configCreated = target.text === null;
|
|
3187
3190
|
}
|
|
3188
3191
|
for (const page of orderedPages()) {
|
|
@@ -3277,7 +3280,7 @@ function assertNoStrandedEntries(m, index, pages, configDir, fileOf) {
|
|
|
3277
3280
|
const stranded = m.join === PATH_JOIN ? [...index.byPath].filter(([abs, values]) => {
|
|
3278
3281
|
const sv = values.get(key);
|
|
3279
3282
|
return sv !== void 0 && sv.collection === m.collection && sv.file === fileOf(abs);
|
|
3280
|
-
}).map(([abs]) => toPosix(
|
|
3283
|
+
}).map(([abs]) => toPosix(relative5(configDir, abs))) : [...index.byField.get(m.join) ?? /* @__PURE__ */ new Map()].filter(([, values]) => {
|
|
3281
3284
|
const sv = values.get(key);
|
|
3282
3285
|
return sv !== void 0 && sv.file === m.file && sv.collection === m.collection;
|
|
3283
3286
|
}).map(([value]) => value);
|
|
@@ -3297,12 +3300,12 @@ async function readManifestText(path, file, perPage) {
|
|
|
3297
3300
|
throw new DocmetaError(`Manifest ${file} could not be read: ${errorMessage(err)}`);
|
|
3298
3301
|
}
|
|
3299
3302
|
}
|
|
3300
|
-
var entryId = (entry,
|
|
3303
|
+
var entryId = (entry, join4) => join4 === PATH_JOIN ? toPosix(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
|
|
3301
3304
|
var lineId = (page, manifest, key) => `${page}\0${manifest}\0${key}`;
|
|
3302
|
-
function keyLines2(
|
|
3305
|
+
function keyLines2(text, join4) {
|
|
3303
3306
|
const out = /* @__PURE__ */ new Map();
|
|
3304
3307
|
const lc = new LineCounter3();
|
|
3305
|
-
const doc = parseDocument4(
|
|
3308
|
+
const doc = parseDocument4(text, { lineCounter: lc, uniqueKeys: false });
|
|
3306
3309
|
const root = doc.contents;
|
|
3307
3310
|
if (!isMap4(root)) return out;
|
|
3308
3311
|
for (const pair of root.items) {
|
|
@@ -3314,15 +3317,15 @@ function keyLines2(text2, join5) {
|
|
|
3314
3317
|
const range = isNode3(kv.key) ? kv.key.range : void 0;
|
|
3315
3318
|
if (range) keys.set(name, lc.linePos(range[0]).line);
|
|
3316
3319
|
}
|
|
3317
|
-
out.set(entryId(spelled,
|
|
3320
|
+
out.set(entryId(spelled, join4), keys);
|
|
3318
3321
|
}
|
|
3319
3322
|
return out;
|
|
3320
3323
|
}
|
|
3321
3324
|
async function configTarget(ctx, configDir) {
|
|
3322
3325
|
if (ctx.configPath !== void 0) {
|
|
3323
3326
|
try {
|
|
3324
|
-
const
|
|
3325
|
-
return { path: ctx.configPath, text
|
|
3327
|
+
const text = await readFile4(ctx.configPath, "utf8");
|
|
3328
|
+
return { path: ctx.configPath, text, disk: text };
|
|
3326
3329
|
} catch (err) {
|
|
3327
3330
|
if (err.code !== "ENOENT") {
|
|
3328
3331
|
throw new DocmetaError(
|
|
@@ -3335,15 +3338,15 @@ async function configTarget(ctx, configDir) {
|
|
|
3335
3338
|
for (const name of FAMILY_CONFIG_NAMES) {
|
|
3336
3339
|
const path = join3(configDir, name);
|
|
3337
3340
|
try {
|
|
3338
|
-
const
|
|
3339
|
-
return { path, text
|
|
3341
|
+
const text = await readFile4(path, "utf8");
|
|
3342
|
+
return { path, text, disk: text };
|
|
3340
3343
|
} catch {
|
|
3341
3344
|
}
|
|
3342
3345
|
}
|
|
3343
3346
|
return { path: join3(configDir, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"), text: null, disk: null };
|
|
3344
3347
|
}
|
|
3345
|
-
function editConfig(
|
|
3346
|
-
const doc = parseDocument4(
|
|
3348
|
+
function editConfig(text, model, source) {
|
|
3349
|
+
const doc = parseDocument4(text ?? "");
|
|
3347
3350
|
const [problem] = doc.errors;
|
|
3348
3351
|
if (problem !== void 0) throw new DocmetaError(`${source}: invalid YAML: ${problem.message}`);
|
|
3349
3352
|
if (doc.contents !== null && !isMap4(doc.contents)) {
|
|
@@ -3515,7 +3518,7 @@ function buildResult(input) {
|
|
|
3515
3518
|
};
|
|
3516
3519
|
}
|
|
3517
3520
|
async function applyRelocation(ctx, plan, io = {}) {
|
|
3518
|
-
const write = io.write ?? ((path,
|
|
3521
|
+
const write = io.write ?? ((path, text) => writeFileAtomic(path, text, { createParents: true }));
|
|
3519
3522
|
const remove = io.remove ?? ((path) => unlink(path));
|
|
3520
3523
|
const label = (w) => w.kind === "config" ? configSource(w.path, ctx.cwd) : reported2(w.path, ctx.base);
|
|
3521
3524
|
for (const w of plan.writes) {
|
|
@@ -3687,12 +3690,6 @@ function relocationOffers(plan) {
|
|
|
3687
3690
|
return plan.offers;
|
|
3688
3691
|
}
|
|
3689
3692
|
|
|
3690
|
-
// src/meta/commands/validate.ts
|
|
3691
|
-
import { resolve as resolve7, extname as extname3 } from "node:path";
|
|
3692
|
-
|
|
3693
|
-
// src/meta/core/derive/provenance.ts
|
|
3694
|
-
import picomatch2 from "picomatch";
|
|
3695
|
-
|
|
3696
3693
|
// src/shared/pin.ts
|
|
3697
3694
|
var LineRangeError = class extends ToolError {
|
|
3698
3695
|
constructor(message) {
|
|
@@ -3700,12 +3697,12 @@ var LineRangeError = class extends ToolError {
|
|
|
3700
3697
|
this.name = "LineRangeError";
|
|
3701
3698
|
}
|
|
3702
3699
|
};
|
|
3703
|
-
function normalizeText(
|
|
3704
|
-
const body =
|
|
3700
|
+
function normalizeText(text) {
|
|
3701
|
+
const body = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
3705
3702
|
return body.replace(/\r\n/g, "\n");
|
|
3706
3703
|
}
|
|
3707
|
-
function splitLines(
|
|
3708
|
-
const parts = normalizeText(
|
|
3704
|
+
function splitLines(text) {
|
|
3705
|
+
const parts = normalizeText(text).split("\n");
|
|
3709
3706
|
if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
|
|
3710
3707
|
return parts;
|
|
3711
3708
|
}
|
|
@@ -3722,8 +3719,8 @@ function sliceLines(lines, range, label) {
|
|
|
3722
3719
|
}
|
|
3723
3720
|
return lines.slice(start - 1, end).join("\n");
|
|
3724
3721
|
}
|
|
3725
|
-
function hashRange(
|
|
3726
|
-
return hashLines(sliceLines(splitLines(
|
|
3722
|
+
function hashRange(text, range, key) {
|
|
3723
|
+
return hashLines(sliceLines(splitLines(text), range), key);
|
|
3727
3724
|
}
|
|
3728
3725
|
function hashLines(joined, key) {
|
|
3729
3726
|
return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
|
|
@@ -3770,14 +3767,14 @@ function findWindows(lines, length, pin, key, opts) {
|
|
|
3770
3767
|
for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
|
|
3771
3768
|
let spent = 0;
|
|
3772
3769
|
for (const start of order) {
|
|
3773
|
-
const
|
|
3774
|
-
const bytes = Buffer.byteLength(
|
|
3770
|
+
const text = joined(start);
|
|
3771
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
3775
3772
|
if (spent + bytes > budget) {
|
|
3776
3773
|
if (counter !== void 0) counter.left = budget - spent;
|
|
3777
3774
|
return { starts, truncated: true };
|
|
3778
3775
|
}
|
|
3779
3776
|
spent += bytes;
|
|
3780
|
-
if (hashLines(
|
|
3777
|
+
if (hashLines(text, key) === pin) starts.push(start);
|
|
3781
3778
|
}
|
|
3782
3779
|
if (counter !== void 0) counter.left = budget - spent;
|
|
3783
3780
|
return { starts, truncated: false };
|
|
@@ -3808,9 +3805,9 @@ function toBodyLines(lines, bodyLine) {
|
|
|
3808
3805
|
return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
|
|
3809
3806
|
}
|
|
3810
3807
|
function splitPageArgument(arg) {
|
|
3811
|
-
const
|
|
3812
|
-
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(
|
|
3813
|
-
const head = m === null ?
|
|
3808
|
+
const text = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
|
|
3809
|
+
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text);
|
|
3810
|
+
const head = m === null ? text : text.slice(0, m.index);
|
|
3814
3811
|
if (m === null || head === "") return { page: arg };
|
|
3815
3812
|
const start = Number(m[1]);
|
|
3816
3813
|
const end = m[2] === void 0 ? start : Number(m[2]);
|
|
@@ -3821,6 +3818,7 @@ function splitPageArgument(arg) {
|
|
|
3821
3818
|
}
|
|
3822
3819
|
|
|
3823
3820
|
// src/meta/core/derive/provenance.ts
|
|
3821
|
+
import picomatch2 from "picomatch";
|
|
3824
3822
|
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
3825
3823
|
var DEFAULT_MACHINES = ["*[bot]"];
|
|
3826
3824
|
var PROVENANCE_POINTER = "/provenance";
|
|
@@ -3875,8 +3873,8 @@ function readProvenancePage(content, opts) {
|
|
|
3875
3873
|
}
|
|
3876
3874
|
var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
|
|
3877
3875
|
var ALL_ZERO = /^0+$/;
|
|
3878
|
-
function parseLinePorcelain(
|
|
3879
|
-
const rows =
|
|
3876
|
+
function parseLinePorcelain(text) {
|
|
3877
|
+
const rows = text.split("\n");
|
|
3880
3878
|
if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
|
|
3881
3879
|
const out = [];
|
|
3882
3880
|
let i = 0;
|
|
@@ -4299,2959 +4297,6 @@ function attributeRange(input) {
|
|
|
4299
4297
|
return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
|
|
4300
4298
|
}
|
|
4301
4299
|
|
|
4302
|
-
// src/meta/core/derive/config.ts
|
|
4303
|
-
var DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
|
|
4304
|
-
function commandsOf(config) {
|
|
4305
|
-
if (config?.commands === void 0) return void 0;
|
|
4306
|
-
const out = {};
|
|
4307
|
-
for (const [field, c] of Object.entries(config.commands)) {
|
|
4308
|
-
out[field] = {
|
|
4309
|
-
run: c.run,
|
|
4310
|
-
timeoutMs: c.timeout === void 0 ? DEFAULT_COMMAND_TIMEOUT_MS : Math.round(c.timeout * 1e3)
|
|
4311
|
-
};
|
|
4312
|
-
}
|
|
4313
|
-
return out;
|
|
4314
|
-
}
|
|
4315
|
-
function machinesOf(config) {
|
|
4316
|
-
return config?.machines ?? DEFAULT_MACHINES;
|
|
4317
|
-
}
|
|
4318
|
-
|
|
4319
|
-
// src/meta/core/derive/index.ts
|
|
4320
|
-
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
4321
|
-
|
|
4322
|
-
// src/meta/core/derive/cache.ts
|
|
4323
|
-
import { createHash } from "node:crypto";
|
|
4324
|
-
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4325
|
-
import { join as join4 } from "node:path";
|
|
4326
|
-
var REVIEW_CACHE_DIR = ".manni/meta/review-cache";
|
|
4327
|
-
var REVIEW_CACHE_VERSION = 1;
|
|
4328
|
-
var ReviewCache = class {
|
|
4329
|
-
constructor(dir, enabled) {
|
|
4330
|
-
this.dir = dir;
|
|
4331
|
-
this.enabled = enabled;
|
|
4332
|
-
}
|
|
4333
|
-
dir;
|
|
4334
|
-
enabled;
|
|
4335
|
-
entryPath(key) {
|
|
4336
|
-
const digest = createHash("sha256").update(key).digest("hex");
|
|
4337
|
-
return join4(this.dir, `${digest}.json`);
|
|
4338
|
-
}
|
|
4339
|
-
/** The cached change, or `undefined` for a miss. Never `null`: nulls are not stored. */
|
|
4340
|
-
get(key) {
|
|
4341
|
-
if (!this.enabled) return void 0;
|
|
4342
|
-
let raw;
|
|
4343
|
-
try {
|
|
4344
|
-
raw = JSON.parse(readFileSync(this.entryPath(key), "utf8"));
|
|
4345
|
-
} catch {
|
|
4346
|
-
return void 0;
|
|
4347
|
-
}
|
|
4348
|
-
if (!isRecord4(raw)) return void 0;
|
|
4349
|
-
if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
|
|
4350
|
-
const change = readChange(raw.change);
|
|
4351
|
-
return change ?? void 0;
|
|
4352
|
-
}
|
|
4353
|
-
set(key, value) {
|
|
4354
|
-
if (!this.enabled) return;
|
|
4355
|
-
const entry = { version: REVIEW_CACHE_VERSION, key, change: value };
|
|
4356
|
-
const path = this.entryPath(key);
|
|
4357
|
-
const tmp = `${path}.${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4358
|
-
try {
|
|
4359
|
-
mkdirSync(this.dir, { recursive: true });
|
|
4360
|
-
writeFileSync(tmp, `${JSON.stringify(entry, null, 2)}
|
|
4361
|
-
`, "utf8");
|
|
4362
|
-
renameSync(tmp, path);
|
|
4363
|
-
} catch {
|
|
4364
|
-
try {
|
|
4365
|
-
rmSync(tmp, { force: true });
|
|
4366
|
-
} catch {
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
};
|
|
4371
|
-
function cachedClient(client, cache, identity) {
|
|
4372
|
-
return {
|
|
4373
|
-
detect: () => client.detect(),
|
|
4374
|
-
status: () => client.status(),
|
|
4375
|
-
async mergedChangeFor(sha) {
|
|
4376
|
-
if (identity === null) return client.mergedChangeFor(sha);
|
|
4377
|
-
const key = `${identity.host}/${identity.project}/${sha}`;
|
|
4378
|
-
const hit = cache.get(key);
|
|
4379
|
-
if (hit !== void 0) return hit;
|
|
4380
|
-
const change = await client.mergedChangeFor(sha);
|
|
4381
|
-
if (change !== null) cache.set(key, change);
|
|
4382
|
-
return change;
|
|
4383
|
-
}
|
|
4384
|
-
};
|
|
4385
|
-
}
|
|
4386
|
-
function isRecord4(v) {
|
|
4387
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4388
|
-
}
|
|
4389
|
-
function readChange(v) {
|
|
4390
|
-
if (!isRecord4(v)) return null;
|
|
4391
|
-
const { id, mergedAt, approvals } = v;
|
|
4392
|
-
if (typeof id !== "number" || typeof mergedAt !== "string") return null;
|
|
4393
|
-
if (!Array.isArray(approvals)) return null;
|
|
4394
|
-
const read = [];
|
|
4395
|
-
for (const a of approvals) {
|
|
4396
|
-
if (!isRecord4(a)) return null;
|
|
4397
|
-
if (typeof a.login !== "string" || typeof a.submittedAt !== "string") return null;
|
|
4398
|
-
read.push({ login: a.login, submittedAt: a.submittedAt });
|
|
4399
|
-
}
|
|
4400
|
-
return { id, mergedAt, approvals: read };
|
|
4401
|
-
}
|
|
4402
|
-
|
|
4403
|
-
// src/meta/core/derive/codeowners.ts
|
|
4404
|
-
import { existsSync } from "node:fs";
|
|
4405
|
-
import { readFile as readFile5 } from "node:fs/promises";
|
|
4406
|
-
import { dirname as dirname3, relative as relative5, resolve as resolve4, sep as sep4 } from "node:path";
|
|
4407
|
-
import picomatch3 from "picomatch";
|
|
4408
|
-
var CODEOWNERS_LOCATIONS = [
|
|
4409
|
-
".github/CODEOWNERS",
|
|
4410
|
-
"CODEOWNERS",
|
|
4411
|
-
"docs/CODEOWNERS",
|
|
4412
|
-
".gitlab/CODEOWNERS"
|
|
4413
|
-
];
|
|
4414
|
-
function findCodeowners(root, explicit) {
|
|
4415
|
-
if (explicit !== void 0) {
|
|
4416
|
-
const path = resolve4(root, explicit);
|
|
4417
|
-
return existsSync(path) ? path : null;
|
|
4418
|
-
}
|
|
4419
|
-
for (const loc of CODEOWNERS_LOCATIONS) {
|
|
4420
|
-
const path = resolve4(root, ...loc.split("/"));
|
|
4421
|
-
if (existsSync(path)) return path;
|
|
4422
|
-
}
|
|
4423
|
-
return null;
|
|
4424
|
-
}
|
|
4425
|
-
var SECTION_HEADER = /^(\^?)\[([^\]]+)\](?:\[(\d+)\])?(.*)$/;
|
|
4426
|
-
var TOKEN = /(?:\\.|[^\s\\])+/g;
|
|
4427
|
-
function parseCodeowners(text2, path, label) {
|
|
4428
|
-
const rules = [];
|
|
4429
|
-
const sections = [];
|
|
4430
|
-
let current;
|
|
4431
|
-
const lines = text2.split(/\r?\n/);
|
|
4432
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
4433
|
-
const raw = lines[i] ?? "";
|
|
4434
|
-
const line = raw.trim();
|
|
4435
|
-
if (line === "" || line.startsWith("#")) continue;
|
|
4436
|
-
if (line.startsWith("[") || line.startsWith("^[")) {
|
|
4437
|
-
const m = SECTION_HEADER.exec(line);
|
|
4438
|
-
if (!m) continue;
|
|
4439
|
-
const [, caret, rawName, approvals, rest] = m;
|
|
4440
|
-
const name = (rawName ?? "").trim();
|
|
4441
|
-
if (name === "") continue;
|
|
4442
|
-
const owners2 = tokens(rest ?? "");
|
|
4443
|
-
const existing = sections.find(
|
|
4444
|
-
(s) => s.name.toLowerCase() === name.toLowerCase()
|
|
4445
|
-
);
|
|
4446
|
-
if (existing) {
|
|
4447
|
-
if (owners2.length > 0) existing.defaultOwners = owners2;
|
|
4448
|
-
current = existing;
|
|
4449
|
-
continue;
|
|
4450
|
-
}
|
|
4451
|
-
const section = {
|
|
4452
|
-
name,
|
|
4453
|
-
optional: caret === "^",
|
|
4454
|
-
...approvals !== void 0 ? { approvals: Number(approvals) } : {},
|
|
4455
|
-
defaultOwners: owners2
|
|
4456
|
-
};
|
|
4457
|
-
sections.push(section);
|
|
4458
|
-
current = section;
|
|
4459
|
-
continue;
|
|
4460
|
-
}
|
|
4461
|
-
const [first, ...owners] = tokens(line);
|
|
4462
|
-
if (first === void 0) continue;
|
|
4463
|
-
const pattern = unescapePattern(first);
|
|
4464
|
-
if (pattern === "" || pattern === "/") continue;
|
|
4465
|
-
rules.push({
|
|
4466
|
-
pattern,
|
|
4467
|
-
owners,
|
|
4468
|
-
line: i + 1,
|
|
4469
|
-
...current ? { section: current.name } : {}
|
|
4470
|
-
});
|
|
4471
|
-
}
|
|
4472
|
-
return { path, label, rules, sections };
|
|
4473
|
-
}
|
|
4474
|
-
function tokens(s) {
|
|
4475
|
-
return s.match(TOKEN) ?? [];
|
|
4476
|
-
}
|
|
4477
|
-
function unescapePattern(token) {
|
|
4478
|
-
return token.replace(/\\ /g, " ").replace(/^\\#/, "#");
|
|
4479
|
-
}
|
|
4480
|
-
function ownersFor(file, relPath) {
|
|
4481
|
-
const matchers = matchersFor(file);
|
|
4482
|
-
const defaults = new Map(file.sections.map((s) => [s.name, s.defaultOwners]));
|
|
4483
|
-
const order = [void 0, ...file.sections.map((s) => s.name)];
|
|
4484
|
-
const winners = /* @__PURE__ */ new Map();
|
|
4485
|
-
file.rules.forEach((rule, i) => {
|
|
4486
|
-
if (matchers[i]?.(relPath)) winners.set(rule.section, rule);
|
|
4487
|
-
});
|
|
4488
|
-
if (winners.size === 0) return null;
|
|
4489
|
-
const owners = [];
|
|
4490
|
-
let line = 0;
|
|
4491
|
-
let lineFromOwner = false;
|
|
4492
|
-
for (const section of order) {
|
|
4493
|
-
const rule = winners.get(section);
|
|
4494
|
-
if (!rule) continue;
|
|
4495
|
-
const chosen = rule.owners.length > 0 ? rule.owners : (section === void 0 ? void 0 : defaults.get(section)) ?? [];
|
|
4496
|
-
if (chosen.length > 0) {
|
|
4497
|
-
line = rule.line;
|
|
4498
|
-
lineFromOwner = true;
|
|
4499
|
-
} else if (!lineFromOwner) {
|
|
4500
|
-
line = rule.line;
|
|
4501
|
-
}
|
|
4502
|
-
for (const o of chosen) if (!owners.includes(o)) owners.push(o);
|
|
4503
|
-
}
|
|
4504
|
-
return { owners, line };
|
|
4505
|
-
}
|
|
4506
|
-
var matcherCache = /* @__PURE__ */ new WeakMap();
|
|
4507
|
-
function matchersFor(file) {
|
|
4508
|
-
let matchers = matcherCache.get(file);
|
|
4509
|
-
if (!matchers) {
|
|
4510
|
-
matchers = file.rules.map((r) => picomatch3(toGlobs(r.pattern), PICOMATCH));
|
|
4511
|
-
matcherCache.set(file, matchers);
|
|
4512
|
-
}
|
|
4513
|
-
return matchers;
|
|
4514
|
-
}
|
|
4515
|
-
var PICOMATCH = { dot: true, windows: false };
|
|
4516
|
-
function toGlobs(pattern) {
|
|
4517
|
-
let p = pattern;
|
|
4518
|
-
const dirOnly = p.endsWith("/");
|
|
4519
|
-
if (dirOnly) p = p.slice(0, -1);
|
|
4520
|
-
let anchored = false;
|
|
4521
|
-
if (p.startsWith("/")) {
|
|
4522
|
-
anchored = true;
|
|
4523
|
-
p = p.slice(1);
|
|
4524
|
-
} else if (p.includes("/") && !p.startsWith("**/")) {
|
|
4525
|
-
anchored = true;
|
|
4526
|
-
}
|
|
4527
|
-
const base = anchored || p.startsWith("**/") ? p : `**/${p}`;
|
|
4528
|
-
if (dirOnly) return [`${base}/**`];
|
|
4529
|
-
const last = p.slice(p.lastIndexOf("/") + 1);
|
|
4530
|
-
return /[*?[]/.test(last) ? [base] : [base, `${base}/**`];
|
|
4531
|
-
}
|
|
4532
|
-
var NOT_FOUND_REASON = `no CODEOWNERS file found (looked for ${CODEOWNERS_LOCATIONS.join(", ")})`;
|
|
4533
|
-
async function deriveFromCodeowners(inputs, opts) {
|
|
4534
|
-
const records = /* @__PURE__ */ new Map();
|
|
4535
|
-
const explicitPath = opts.explicit === void 0 ? void 0 : resolve4(opts.configDir ?? process.cwd(), opts.explicit);
|
|
4536
|
-
if (explicitPath !== void 0 && !existsSync(explicitPath)) {
|
|
4537
|
-
return {
|
|
4538
|
-
status: { available: false, reason: `CODEOWNERS not found at ${explicitPath}` },
|
|
4539
|
-
records
|
|
4540
|
-
};
|
|
4541
|
-
}
|
|
4542
|
-
const byRoot = /* @__PURE__ */ new Map();
|
|
4543
|
-
const rootCache = /* @__PURE__ */ new Map();
|
|
4544
|
-
for (const input of inputs) {
|
|
4545
|
-
const dir = dirname3(input.absPath);
|
|
4546
|
-
let root = rootCache.get(dir);
|
|
4547
|
-
if (root === void 0) {
|
|
4548
|
-
root = opts.rootOf(input.absPath);
|
|
4549
|
-
rootCache.set(dir, root);
|
|
4550
|
-
}
|
|
4551
|
-
if (root === null) continue;
|
|
4552
|
-
const bucket = byRoot.get(root);
|
|
4553
|
-
if (bucket) bucket.push(input);
|
|
4554
|
-
else byRoot.set(root, [input]);
|
|
4555
|
-
}
|
|
4556
|
-
let found = 0;
|
|
4557
|
-
const parsed = /* @__PURE__ */ new Map();
|
|
4558
|
-
for (const [root, bucket] of byRoot) {
|
|
4559
|
-
const path = explicitPath ?? findCodeowners(root);
|
|
4560
|
-
if (path === null) {
|
|
4561
|
-
for (const input of bucket) records.set(input.label, null);
|
|
4562
|
-
continue;
|
|
4563
|
-
}
|
|
4564
|
-
found += 1;
|
|
4565
|
-
const label = toPosix2(relative5(opts.configDir ?? root, path));
|
|
4566
|
-
const key = `${path}\0${label}`;
|
|
4567
|
-
let file = parsed.get(key);
|
|
4568
|
-
if (!file) {
|
|
4569
|
-
file = readFile5(path, "utf8").then(
|
|
4570
|
-
(text2) => parseCodeowners(text2, path, label)
|
|
4571
|
-
);
|
|
4572
|
-
parsed.set(key, file);
|
|
4573
|
-
}
|
|
4574
|
-
let owners;
|
|
4575
|
-
try {
|
|
4576
|
-
owners = await file;
|
|
4577
|
-
} catch (err) {
|
|
4578
|
-
return {
|
|
4579
|
-
status: {
|
|
4580
|
-
available: false,
|
|
4581
|
-
reason: `CODEOWNERS at ${label} could not be read: ${errorMessage(err)}`
|
|
4582
|
-
},
|
|
4583
|
-
records
|
|
4584
|
-
};
|
|
4585
|
-
}
|
|
4586
|
-
for (const input of bucket) {
|
|
4587
|
-
const rel = toPosix2(relative5(root, input.absPath));
|
|
4588
|
-
const hit = ownersFor(owners, rel);
|
|
4589
|
-
records.set(
|
|
4590
|
-
input.label,
|
|
4591
|
-
hit === null ? null : {
|
|
4592
|
-
value: hit.owners,
|
|
4593
|
-
source: "codeowners",
|
|
4594
|
-
evidence: `${label}:${hit.line}`
|
|
4595
|
-
}
|
|
4596
|
-
);
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
if (byRoot.size === 0) {
|
|
4600
|
-
return {
|
|
4601
|
-
status: { available: false, reason: "no git repository contains the documents" },
|
|
4602
|
-
records
|
|
4603
|
-
};
|
|
4604
|
-
}
|
|
4605
|
-
if (found === 0 && explicitPath === void 0) {
|
|
4606
|
-
return { status: { available: true, reason: NOT_FOUND_REASON }, records };
|
|
4607
|
-
}
|
|
4608
|
-
return { status: { available: true }, records };
|
|
4609
|
-
}
|
|
4610
|
-
function toPosix2(p) {
|
|
4611
|
-
return p.split(sep4).join("/");
|
|
4612
|
-
}
|
|
4613
|
-
|
|
4614
|
-
// src/meta/core/derive/spawn.ts
|
|
4615
|
-
import { spawn } from "node:child_process";
|
|
4616
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
4617
|
-
var MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
4618
|
-
var BinMissing = class extends Error {
|
|
4619
|
-
};
|
|
4620
|
-
function run(bin, args, opts) {
|
|
4621
|
-
return new Promise((settle2, reject) => {
|
|
4622
|
-
let done = false;
|
|
4623
|
-
const finish = (r) => {
|
|
4624
|
-
if (done) return;
|
|
4625
|
-
done = true;
|
|
4626
|
-
settle2(r);
|
|
4627
|
-
};
|
|
4628
|
-
const fail = (err) => {
|
|
4629
|
-
if (done) return;
|
|
4630
|
-
done = true;
|
|
4631
|
-
reject(err);
|
|
4632
|
-
};
|
|
4633
|
-
let child;
|
|
4634
|
-
try {
|
|
4635
|
-
child = spawn(bin, [...opts.prefixArgs ?? [], ...args], {
|
|
4636
|
-
cwd: opts.cwd,
|
|
4637
|
-
windowsHide: true
|
|
4638
|
-
});
|
|
4639
|
-
} catch (err) {
|
|
4640
|
-
fail(err instanceof Error ? err : new Error(String(err)));
|
|
4641
|
-
return;
|
|
4642
|
-
}
|
|
4643
|
-
let stdout = "";
|
|
4644
|
-
let stderr = "";
|
|
4645
|
-
let timedOut = false;
|
|
4646
|
-
let tooLarge = false;
|
|
4647
|
-
const stop = (r) => {
|
|
4648
|
-
clearTimeout(timer);
|
|
4649
|
-
child.kill();
|
|
4650
|
-
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
4651
|
-
finish({ code: null, ...r });
|
|
4652
|
-
};
|
|
4653
|
-
child.stdout.setEncoding("utf8");
|
|
4654
|
-
child.stdout.on("data", (chunk) => {
|
|
4655
|
-
stdout += chunk;
|
|
4656
|
-
if (stdout.length > MAX_OUTPUT_BYTES) {
|
|
4657
|
-
tooLarge = true;
|
|
4658
|
-
stop({ stdout, stderr, timedOut, tooLarge });
|
|
4659
|
-
}
|
|
4660
|
-
});
|
|
4661
|
-
child.stderr.setEncoding("utf8");
|
|
4662
|
-
child.stderr.on("data", (chunk) => {
|
|
4663
|
-
stderr += chunk;
|
|
4664
|
-
if (stderr.length > MAX_OUTPUT_BYTES) {
|
|
4665
|
-
tooLarge = true;
|
|
4666
|
-
stop({ stdout, stderr, timedOut, tooLarge });
|
|
4667
|
-
}
|
|
4668
|
-
});
|
|
4669
|
-
child.stdin.end();
|
|
4670
|
-
const timer = setTimeout(() => {
|
|
4671
|
-
timedOut = true;
|
|
4672
|
-
stop({ stdout, stderr, timedOut, tooLarge });
|
|
4673
|
-
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
4674
|
-
child.on("error", (err) => {
|
|
4675
|
-
clearTimeout(timer);
|
|
4676
|
-
fail(err.code === "ENOENT" ? new BinMissing(err.message) : err);
|
|
4677
|
-
});
|
|
4678
|
-
child.on("close", (code) => {
|
|
4679
|
-
clearTimeout(timer);
|
|
4680
|
-
finish({ code, stdout, stderr, timedOut, tooLarge });
|
|
4681
|
-
});
|
|
4682
|
-
});
|
|
4683
|
-
}
|
|
4684
|
-
function commandLine(bin, args) {
|
|
4685
|
-
return [bin, ...args].join(" ");
|
|
4686
|
-
}
|
|
4687
|
-
function lastLine(text2) {
|
|
4688
|
-
const lines = text2.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
4689
|
-
return lines.at(-1) ?? "";
|
|
4690
|
-
}
|
|
4691
|
-
|
|
4692
|
-
// src/meta/core/derive/command.ts
|
|
4693
|
-
var PATH_PLACEHOLDER = "{path}";
|
|
4694
|
-
function isPerFile(command) {
|
|
4695
|
-
return command.run.some((arg) => arg.includes(PATH_PLACEHOLDER));
|
|
4696
|
-
}
|
|
4697
|
-
function argvFor(command, label) {
|
|
4698
|
-
return command.run.map((arg) => arg.split(PATH_PLACEHOLDER).join(label));
|
|
4699
|
-
}
|
|
4700
|
-
function valueOf(stdout) {
|
|
4701
|
-
const text2 = stdout.trim();
|
|
4702
|
-
if (text2 === "") return null;
|
|
4703
|
-
if (!/^[{["]/.test(text2)) return text2;
|
|
4704
|
-
try {
|
|
4705
|
-
return JSON.parse(text2);
|
|
4706
|
-
} catch {
|
|
4707
|
-
return text2;
|
|
4708
|
-
}
|
|
4709
|
-
}
|
|
4710
|
-
async function deriveFromCommands(inputs, commands, opts) {
|
|
4711
|
-
const records = /* @__PURE__ */ new Map();
|
|
4712
|
-
const fields = Object.keys(commands);
|
|
4713
|
-
if (fields.length === 0 || inputs.length === 0) {
|
|
4714
|
-
return { status: { available: true }, records };
|
|
4715
|
-
}
|
|
4716
|
-
for (const input of inputs) records.set(input.label, {});
|
|
4717
|
-
for (const field of fields) {
|
|
4718
|
-
const command = commands[field];
|
|
4719
|
-
if (command === void 0) continue;
|
|
4720
|
-
if (isPerFile(command)) {
|
|
4721
|
-
for (const [label, record] of records) {
|
|
4722
|
-
const outcome = await runOne(field, argvFor(command, label), command.timeoutMs, opts);
|
|
4723
|
-
if ("reason" in outcome) return unavailable(outcome.reason);
|
|
4724
|
-
record[field] = outcome.value;
|
|
4725
|
-
}
|
|
4726
|
-
} else {
|
|
4727
|
-
const outcome = await runOne(field, command.run, command.timeoutMs, opts);
|
|
4728
|
-
if ("reason" in outcome) return unavailable(outcome.reason);
|
|
4729
|
-
for (const record of records.values()) record[field] = outcome.value;
|
|
4730
|
-
}
|
|
4731
|
-
}
|
|
4732
|
-
return { status: { available: true }, records };
|
|
4733
|
-
}
|
|
4734
|
-
function unavailable(reason) {
|
|
4735
|
-
return { status: { available: false, reason }, records: /* @__PURE__ */ new Map() };
|
|
4736
|
-
}
|
|
4737
|
-
async function runOne(field, argv, timeoutMs, opts) {
|
|
4738
|
-
const [program, ...args] = argv;
|
|
4739
|
-
const where = `(derive.commands.${field})`;
|
|
4740
|
-
if (program === void 0) {
|
|
4741
|
-
return { reason: `no program to run ${where}` };
|
|
4742
|
-
}
|
|
4743
|
-
const line = argv.join(" ");
|
|
4744
|
-
let result;
|
|
4745
|
-
try {
|
|
4746
|
-
result = await run(opts.spawn?.bin ?? program, args, {
|
|
4747
|
-
...opts.spawn,
|
|
4748
|
-
cwd: opts.spawn?.cwd ?? opts.cwd,
|
|
4749
|
-
timeoutMs
|
|
4750
|
-
});
|
|
4751
|
-
} catch (err) {
|
|
4752
|
-
if (err instanceof BinMissing) {
|
|
4753
|
-
return { reason: `\`${program}\` is not on PATH ${where}` };
|
|
4754
|
-
}
|
|
4755
|
-
return { reason: `\`${line}\` could not be run: ${errorMessage(err)} ${where}` };
|
|
4756
|
-
}
|
|
4757
|
-
if (result.timedOut) {
|
|
4758
|
-
return { reason: `\`${line}\` timed out after ${String(timeoutMs / 1e3)}s ${where}` };
|
|
4759
|
-
}
|
|
4760
|
-
if (result.tooLarge) {
|
|
4761
|
-
return {
|
|
4762
|
-
reason: `\`${line}\` wrote more than ${String(MAX_OUTPUT_BYTES / (1024 * 1024))} MiB; a command reports one field's value ${where}`
|
|
4763
|
-
};
|
|
4764
|
-
}
|
|
4765
|
-
if (result.code !== 0) {
|
|
4766
|
-
const last = lastLine(result.stderr);
|
|
4767
|
-
const detail = last === "" ? "no output on stderr" : last;
|
|
4768
|
-
const exit = result.code === null ? "killed by a signal" : `exit ${String(result.code)}`;
|
|
4769
|
-
return { reason: `\`${line}\` failed (${exit}): ${detail} ${where}` };
|
|
4770
|
-
}
|
|
4771
|
-
const value = valueOf(result.stdout);
|
|
4772
|
-
return { value: value === null ? null : { value, source: "command", evidence: line } };
|
|
4773
|
-
}
|
|
4774
|
-
|
|
4775
|
-
// src/meta/core/derive/reviews.ts
|
|
4776
|
-
import { resolve as resolvePath2 } from "node:path";
|
|
4777
|
-
function parseOriginUrl(url) {
|
|
4778
|
-
const trimmed = url.trim();
|
|
4779
|
-
if (trimmed === "") return null;
|
|
4780
|
-
if (isLocalPath(trimmed)) return null;
|
|
4781
|
-
const scp = /^(?:[^@/:]+@)?([^/:]+):(?!\/\/)(.+)$/.exec(trimmed);
|
|
4782
|
-
if (scp) {
|
|
4783
|
-
const host = scp[1];
|
|
4784
|
-
const path = scp[2];
|
|
4785
|
-
if (host === void 0 || path === void 0) return null;
|
|
4786
|
-
if (/^[A-Za-z]$/.test(host)) return null;
|
|
4787
|
-
return project(host, path);
|
|
4788
|
-
}
|
|
4789
|
-
let parsed;
|
|
4790
|
-
try {
|
|
4791
|
-
parsed = new URL(trimmed);
|
|
4792
|
-
} catch {
|
|
4793
|
-
return null;
|
|
4794
|
-
}
|
|
4795
|
-
if (!/^(https?|ssh|git):$/.test(parsed.protocol)) return null;
|
|
4796
|
-
if (parsed.hostname === "") return null;
|
|
4797
|
-
return project(parsed.hostname, parsed.pathname);
|
|
4798
|
-
}
|
|
4799
|
-
function isLocalPath(s) {
|
|
4800
|
-
return s.startsWith("/") || s.startsWith("./") || s.startsWith("../") || s.startsWith("\\\\");
|
|
4801
|
-
}
|
|
4802
|
-
function project(host, path) {
|
|
4803
|
-
const segments2 = path.replace(/\.git$/, "").split("/").filter((s) => s !== "");
|
|
4804
|
-
if (segments2.length < 2) return null;
|
|
4805
|
-
return { host, project: segments2.join("/") };
|
|
4806
|
-
}
|
|
4807
|
-
function identityFromOrigin(host, project2, requested) {
|
|
4808
|
-
const name = host.toLowerCase();
|
|
4809
|
-
if (name.includes("github")) return { kind: "github", host, project: project2 };
|
|
4810
|
-
if (name.includes("gitlab")) return { kind: "gitlab", host, project: project2 };
|
|
4811
|
-
const only = requested !== void 0 && requested.length === 1 ? requested[0] : void 0;
|
|
4812
|
-
return only === void 0 ? null : { kind: only, host, project: project2 };
|
|
4813
|
-
}
|
|
4814
|
-
var UnknownHostClient = class {
|
|
4815
|
-
constructor(host) {
|
|
4816
|
-
this.host = host;
|
|
4817
|
-
}
|
|
4818
|
-
host;
|
|
4819
|
-
detect() {
|
|
4820
|
-
return Promise.resolve(null);
|
|
4821
|
-
}
|
|
4822
|
-
status() {
|
|
4823
|
-
return Promise.resolve({
|
|
4824
|
-
available: false,
|
|
4825
|
-
reason: `the origin remote is ${this.host}, which names neither GitHub nor GitLab; list exactly one of github, gitlab in derive.sources to say which it is`
|
|
4826
|
-
});
|
|
4827
|
-
}
|
|
4828
|
-
mergedChangeFor() {
|
|
4829
|
-
return Promise.resolve(null);
|
|
4830
|
-
}
|
|
4831
|
-
};
|
|
4832
|
-
function httpStatus(stderr) {
|
|
4833
|
-
const m = /\b(?:HTTP\s+(\d{3})|(\d{3})\s+Not\s+Found)\b/i.exec(stderr);
|
|
4834
|
-
const code = m?.[1] ?? m?.[2];
|
|
4835
|
-
return code === void 0 ? null : Number(code);
|
|
4836
|
-
}
|
|
4837
|
-
function isNotFound(r) {
|
|
4838
|
-
const status = httpStatus(r.stderr);
|
|
4839
|
-
return status === 404 || status === 422;
|
|
4840
|
-
}
|
|
4841
|
-
var CliClient = class {
|
|
4842
|
-
constructor(identity, spawnOpts) {
|
|
4843
|
-
this.identity = identity;
|
|
4844
|
-
this.spawnOpts = spawnOpts;
|
|
4845
|
-
}
|
|
4846
|
-
identity;
|
|
4847
|
-
spawnOpts;
|
|
4848
|
-
detect() {
|
|
4849
|
-
return Promise.resolve(this.identity);
|
|
4850
|
-
}
|
|
4851
|
-
async status() {
|
|
4852
|
-
if (this.identity === null) {
|
|
4853
|
-
return { available: false, reason: "no origin remote to tell GitHub from GitLab" };
|
|
4854
|
-
}
|
|
4855
|
-
const { host } = this.identity;
|
|
4856
|
-
let r;
|
|
4857
|
-
try {
|
|
4858
|
-
r = await this.run(["auth", "status", "--hostname", host]);
|
|
4859
|
-
} catch (err) {
|
|
4860
|
-
if (err instanceof BinMissing) {
|
|
4861
|
-
return {
|
|
4862
|
-
available: false,
|
|
4863
|
-
reason: `${this.tool} is not on PATH (origin is ${host}); install ${this.tool} and run ${this.tool} auth login`
|
|
4864
|
-
};
|
|
4865
|
-
}
|
|
4866
|
-
throw err;
|
|
4867
|
-
}
|
|
4868
|
-
if (r.timedOut) throw this.timeout(["auth", "status", "--hostname", host]);
|
|
4869
|
-
if (r.code !== 0) {
|
|
4870
|
-
return {
|
|
4871
|
-
available: false,
|
|
4872
|
-
reason: `${this.tool} is not authenticated for ${host}; run ${this.tool} auth login`
|
|
4873
|
-
};
|
|
4874
|
-
}
|
|
4875
|
-
return { available: true };
|
|
4876
|
-
}
|
|
4877
|
-
async mergedChangeFor(sha) {
|
|
4878
|
-
if (this.identity === null) return null;
|
|
4879
|
-
return this.lookup(this.identity, sha);
|
|
4880
|
-
}
|
|
4881
|
-
get bin() {
|
|
4882
|
-
return this.spawnOpts.bin ?? this.tool;
|
|
4883
|
-
}
|
|
4884
|
-
run(args) {
|
|
4885
|
-
return run(this.bin, args, this.spawnOpts);
|
|
4886
|
-
}
|
|
4887
|
-
timeout(args) {
|
|
4888
|
-
const ms = this.spawnOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
4889
|
-
return new DocmetaError(
|
|
4890
|
-
`${commandLine(this.tool, args)} timed out after ${ms}ms`
|
|
4891
|
-
);
|
|
4892
|
-
}
|
|
4893
|
-
/**
|
|
4894
|
-
* Run a call whose stdout is JSON. A not-found answer is `null`; any other
|
|
4895
|
-
* failure throws, naming the command the user can re-run by hand. The
|
|
4896
|
-
* message quotes the tool's own name rather than `bin`, which under test
|
|
4897
|
-
* is `node`.
|
|
4898
|
-
*/
|
|
4899
|
-
async json(args) {
|
|
4900
|
-
let r;
|
|
4901
|
-
try {
|
|
4902
|
-
r = await this.run(args);
|
|
4903
|
-
} catch (err) {
|
|
4904
|
-
if (err instanceof BinMissing) {
|
|
4905
|
-
throw new DocmetaError(
|
|
4906
|
-
`${commandLine(this.tool, args)} failed: ${this.tool} is not on PATH`
|
|
4907
|
-
);
|
|
4908
|
-
}
|
|
4909
|
-
throw err;
|
|
4910
|
-
}
|
|
4911
|
-
const cmd = commandLine(this.tool, args);
|
|
4912
|
-
if (r.timedOut) throw this.timeout(args);
|
|
4913
|
-
if (r.tooLarge) {
|
|
4914
|
-
throw new DocmetaError(`${cmd} wrote more output than manni will read`);
|
|
4915
|
-
}
|
|
4916
|
-
if (r.code !== 0) {
|
|
4917
|
-
if (isNotFound(r)) return null;
|
|
4918
|
-
const detail = lastLine(r.stderr) || lastLine(r.stdout);
|
|
4919
|
-
const exit = r.code === null ? "signal" : `exit ${r.code}`;
|
|
4920
|
-
throw new DocmetaError(
|
|
4921
|
-
`${cmd} failed (${exit})${detail === "" ? "" : `: ${detail}`}`
|
|
4922
|
-
);
|
|
4923
|
-
}
|
|
4924
|
-
try {
|
|
4925
|
-
return JSON.parse(r.stdout);
|
|
4926
|
-
} catch {
|
|
4927
|
-
throw new DocmetaError(`${cmd} did not return JSON`);
|
|
4928
|
-
}
|
|
4929
|
-
}
|
|
4930
|
-
};
|
|
4931
|
-
function isRecord5(v) {
|
|
4932
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4933
|
-
}
|
|
4934
|
-
function items(v) {
|
|
4935
|
-
return Array.isArray(v) ? v.filter(isRecord5) : [];
|
|
4936
|
-
}
|
|
4937
|
-
function numberOf(v) {
|
|
4938
|
-
return typeof v === "number" && Number.isInteger(v) ? v : null;
|
|
4939
|
-
}
|
|
4940
|
-
function stringOf(v) {
|
|
4941
|
-
return typeof v === "string" && v !== "" ? v : null;
|
|
4942
|
-
}
|
|
4943
|
-
function timestampOf(v) {
|
|
4944
|
-
if (v === void 0) return void 0;
|
|
4945
|
-
return stringOf(v);
|
|
4946
|
-
}
|
|
4947
|
-
function normalizeLogin(login) {
|
|
4948
|
-
return login.replace(/\[bot\]$/, "");
|
|
4949
|
-
}
|
|
4950
|
-
function latestApprovals(reviews) {
|
|
4951
|
-
const latest = /* @__PURE__ */ new Map();
|
|
4952
|
-
for (const r of reviews) {
|
|
4953
|
-
if (r.state !== "APPROVED") continue;
|
|
4954
|
-
const key = normalizeLogin(r.login);
|
|
4955
|
-
const seen = latest.get(key);
|
|
4956
|
-
if (seen === void 0 || r.submittedAt > seen.submittedAt) {
|
|
4957
|
-
latest.set(key, { login: r.login, submittedAt: r.submittedAt });
|
|
4958
|
-
}
|
|
4959
|
-
}
|
|
4960
|
-
return byTime([...latest.values()]);
|
|
4961
|
-
}
|
|
4962
|
-
function byTime(approvals) {
|
|
4963
|
-
return [...approvals].sort(
|
|
4964
|
-
(a, b) => a.submittedAt < b.submittedAt ? -1 : a.submittedAt > b.submittedAt ? 1 : 0
|
|
4965
|
-
);
|
|
4966
|
-
}
|
|
4967
|
-
var GitHubClient = class extends CliClient {
|
|
4968
|
-
tool = "gh";
|
|
4969
|
-
async lookup(identity, sha) {
|
|
4970
|
-
const { host, project: project2 } = identity;
|
|
4971
|
-
const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
|
|
4972
|
-
let candidates = items(await api(`repos/${project2}/commits/${sha}/pulls`)).map(
|
|
4973
|
-
(p) => ({ number: numberOf(p.number), mergedAt: timestampOf(p.merged_at) })
|
|
4974
|
-
);
|
|
4975
|
-
if (candidates.length === 0) {
|
|
4976
|
-
const found = await this.json([
|
|
4977
|
-
"pr",
|
|
4978
|
-
"list",
|
|
4979
|
-
"--search",
|
|
4980
|
-
sha,
|
|
4981
|
-
"--state",
|
|
4982
|
-
"merged",
|
|
4983
|
-
"--json",
|
|
4984
|
-
"number,mergedAt",
|
|
4985
|
-
"-R",
|
|
4986
|
-
`${host}/${project2}`
|
|
4987
|
-
]);
|
|
4988
|
-
candidates = items(found).map((p) => ({
|
|
4989
|
-
number: numberOf(p.number),
|
|
4990
|
-
mergedAt: timestampOf(p.mergedAt)
|
|
4991
|
-
}));
|
|
4992
|
-
}
|
|
4993
|
-
for (const c of candidates) {
|
|
4994
|
-
if (c.number === null) continue;
|
|
4995
|
-
let mergedAt = c.mergedAt;
|
|
4996
|
-
if (mergedAt === void 0) {
|
|
4997
|
-
const pr = await api(`repos/${project2}/pulls/${c.number}`);
|
|
4998
|
-
mergedAt = isRecord5(pr) ? stringOf(pr.merged_at) ?? null : null;
|
|
4999
|
-
}
|
|
5000
|
-
if (mergedAt === null) continue;
|
|
5001
|
-
const raw = [];
|
|
5002
|
-
for (let page = 1; ; page += 1) {
|
|
5003
|
-
const batch = items(
|
|
5004
|
-
await api(`repos/${project2}/pulls/${c.number}/reviews?per_page=100&page=${page}`)
|
|
5005
|
-
);
|
|
5006
|
-
raw.push(...batch);
|
|
5007
|
-
if (batch.length < 100) break;
|
|
5008
|
-
}
|
|
5009
|
-
const reviews = raw.map((r) => ({
|
|
5010
|
-
login: isRecord5(r.user) ? stringOf(r.user.login) : null,
|
|
5011
|
-
state: stringOf(r.state),
|
|
5012
|
-
submittedAt: stringOf(r.submitted_at)
|
|
5013
|
-
})).filter(
|
|
5014
|
-
(r) => r.login !== null && r.state !== null && r.submittedAt !== null
|
|
5015
|
-
);
|
|
5016
|
-
return { id: c.number, mergedAt, approvals: latestApprovals(reviews) };
|
|
5017
|
-
}
|
|
5018
|
-
return null;
|
|
5019
|
-
}
|
|
5020
|
-
};
|
|
5021
|
-
var GitLabClient = class extends CliClient {
|
|
5022
|
-
tool = "glab";
|
|
5023
|
-
async lookup(identity, sha) {
|
|
5024
|
-
const { host } = identity;
|
|
5025
|
-
const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
|
|
5026
|
-
const merged = items(
|
|
5027
|
-
await api(`projects/:fullpath/repository/commits/${sha}/merge_requests`)
|
|
5028
|
-
).map((mr) => ({ iid: numberOf(mr.iid), mergedAt: stringOf(mr.merged_at) })).find(
|
|
5029
|
-
(mr) => mr.iid !== null && mr.mergedAt !== null
|
|
5030
|
-
);
|
|
5031
|
-
if (merged === void 0) return null;
|
|
5032
|
-
const answer = await api(`projects/:fullpath/merge_requests/${merged.iid}/approvals`);
|
|
5033
|
-
const approvedBy = isRecord5(answer) ? items(answer.approved_by) : [];
|
|
5034
|
-
const approvals = [];
|
|
5035
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5036
|
-
for (const entry of approvedBy) {
|
|
5037
|
-
const login = isRecord5(entry.user) ? stringOf(entry.user.username) : null;
|
|
5038
|
-
if (login === null) continue;
|
|
5039
|
-
const key = normalizeLogin(login);
|
|
5040
|
-
if (seen.has(key)) continue;
|
|
5041
|
-
seen.add(key);
|
|
5042
|
-
approvals.push({ login, submittedAt: merged.mergedAt });
|
|
5043
|
-
}
|
|
5044
|
-
return { id: merged.iid, mergedAt: merged.mergedAt, approvals };
|
|
5045
|
-
}
|
|
5046
|
-
};
|
|
5047
|
-
async function createReviewClient(root, opts) {
|
|
5048
|
-
const spawnOpts = { ...opts.spawn, cwd: opts.spawn?.cwd ?? root };
|
|
5049
|
-
const origin = await originUrl(root);
|
|
5050
|
-
const parsed = origin === null ? null : parseOriginUrl(origin);
|
|
5051
|
-
const identity = parsed === null ? null : identityFromOrigin(parsed.host, parsed.project, opts.requested);
|
|
5052
|
-
if (parsed !== null && identity === null) return new UnknownHostClient(parsed.host);
|
|
5053
|
-
const client = identity?.kind === "gitlab" ? new GitLabClient(identity, spawnOpts) : new GitHubClient(identity, spawnOpts);
|
|
5054
|
-
const cache = new ReviewCache(resolvePath2(root, opts.cacheDir), opts.cache);
|
|
5055
|
-
return cachedClient(client, cache, identity);
|
|
5056
|
-
}
|
|
5057
|
-
async function originUrl(root) {
|
|
5058
|
-
try {
|
|
5059
|
-
const r = await run("git", ["remote", "get-url", "origin"], { cwd: root });
|
|
5060
|
-
if (r.code !== 0 || r.timedOut || r.tooLarge) return null;
|
|
5061
|
-
return stringOf(r.stdout.trim());
|
|
5062
|
-
} catch {
|
|
5063
|
-
return null;
|
|
5064
|
-
}
|
|
5065
|
-
}
|
|
5066
|
-
var NOTHING = { "reviewed-by": null, "last-reviewed": null };
|
|
5067
|
-
async function deriveFromReviews(inputs, client) {
|
|
5068
|
-
const records = /* @__PURE__ */ new Map();
|
|
5069
|
-
const status = await client.status();
|
|
5070
|
-
if (!status.available) return { status, records };
|
|
5071
|
-
const identity = await client.detect();
|
|
5072
|
-
const kind = identity?.kind ?? "github";
|
|
5073
|
-
const bySha = /* @__PURE__ */ new Map();
|
|
5074
|
-
const changeFor = (sha) => {
|
|
5075
|
-
let pending = bySha.get(sha);
|
|
5076
|
-
if (pending === void 0) {
|
|
5077
|
-
pending = client.mergedChangeFor(sha);
|
|
5078
|
-
bySha.set(sha, pending);
|
|
5079
|
-
}
|
|
5080
|
-
return pending;
|
|
5081
|
-
};
|
|
5082
|
-
for (const input of inputs) {
|
|
5083
|
-
if (input.sha === null) {
|
|
5084
|
-
records.set(input.label, NOTHING);
|
|
5085
|
-
continue;
|
|
5086
|
-
}
|
|
5087
|
-
const change = await changeFor(input.sha);
|
|
5088
|
-
records.set(input.label, factsFrom(change, kind));
|
|
5089
|
-
}
|
|
5090
|
-
return { status, records };
|
|
5091
|
-
}
|
|
5092
|
-
function factsFrom(change, kind) {
|
|
5093
|
-
if (change === null || change.approvals.length === 0) return NOTHING;
|
|
5094
|
-
const ordered = byTime(change.approvals);
|
|
5095
|
-
const latest = ordered.at(-1);
|
|
5096
|
-
if (latest === void 0) return NOTHING;
|
|
5097
|
-
const evidence = kind === "gitlab" ? `gitlab MR !${change.id}` : `github PR #${change.id}`;
|
|
5098
|
-
const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
|
|
5099
|
-
return {
|
|
5100
|
-
"reviewed-by": {
|
|
5101
|
-
value: ordered.map((a) => a.login),
|
|
5102
|
-
source: kind,
|
|
5103
|
-
evidence
|
|
5104
|
-
},
|
|
5105
|
-
"last-reviewed": {
|
|
5106
|
-
value: utcDate(latest.submittedAt),
|
|
5107
|
-
source: kind,
|
|
5108
|
-
evidence: dateEvidence
|
|
5109
|
-
}
|
|
5110
|
-
};
|
|
5111
|
-
}
|
|
5112
|
-
function utcDate(iso) {
|
|
5113
|
-
const ms = Date.parse(iso);
|
|
5114
|
-
if (Number.isNaN(ms)) return iso;
|
|
5115
|
-
return new Date(ms).toISOString().slice(0, 10);
|
|
5116
|
-
}
|
|
5117
|
-
|
|
5118
|
-
// src/meta/core/derive/git.ts
|
|
5119
|
-
import { constants as bufferConstants } from "node:buffer";
|
|
5120
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
5121
|
-
import { availableParallelism } from "node:os";
|
|
5122
|
-
import { dirname as dirname4, extname as extname2, isAbsolute as isAbsolute4, posix as posix4, relative as relative6, sep as sep5 } from "node:path";
|
|
5123
|
-
import { parse as parseYaml3 } from "yaml";
|
|
5124
|
-
|
|
5125
|
-
// src/shared/concurrency.ts
|
|
5126
|
-
async function mapConcurrent(items2, limit, fn) {
|
|
5127
|
-
const out = new Array(items2.length);
|
|
5128
|
-
let next = 0;
|
|
5129
|
-
const workers = Array.from(
|
|
5130
|
-
{ length: Math.max(1, Math.min(limit, items2.length)) },
|
|
5131
|
-
async () => {
|
|
5132
|
-
for (; ; ) {
|
|
5133
|
-
const i = next++;
|
|
5134
|
-
const item = items2[i];
|
|
5135
|
-
if (i >= items2.length || item === void 0) return;
|
|
5136
|
-
out[i] = await fn(item);
|
|
5137
|
-
}
|
|
5138
|
-
}
|
|
5139
|
-
);
|
|
5140
|
-
await Promise.all(workers);
|
|
5141
|
-
return out;
|
|
5142
|
-
}
|
|
5143
|
-
|
|
5144
|
-
// src/meta/core/derive/git.ts
|
|
5145
|
-
var DEFAULT_BULK_THRESHOLD = 32;
|
|
5146
|
-
var NULL_SHA = "0".repeat(40);
|
|
5147
|
-
var MAX_GIT_OUTPUT_BYTES = Math.min(
|
|
5148
|
-
512 * 1024 * 1024,
|
|
5149
|
-
bufferConstants.MAX_STRING_LENGTH
|
|
5150
|
-
);
|
|
5151
|
-
var HistoryTooLarge = class extends Error {
|
|
5152
|
-
};
|
|
5153
|
-
var BlobsUnreadable = class extends Error {
|
|
5154
|
-
constructor(detail) {
|
|
5155
|
-
super(detail);
|
|
5156
|
-
this.detail = detail;
|
|
5157
|
-
}
|
|
5158
|
-
detail;
|
|
5159
|
-
};
|
|
5160
|
-
var BlameUnreadable = class extends Error {
|
|
5161
|
-
constructor(rel, detail) {
|
|
5162
|
-
super(rel);
|
|
5163
|
-
this.rel = rel;
|
|
5164
|
-
this.detail = detail;
|
|
5165
|
-
}
|
|
5166
|
-
rel;
|
|
5167
|
-
detail;
|
|
5168
|
-
};
|
|
5169
|
-
function bodyOf(content, fenced) {
|
|
5170
|
-
if (fenced !== true) return content;
|
|
5171
|
-
const loc = locateFrontmatter(content);
|
|
5172
|
-
if (!loc) return content;
|
|
5173
|
-
return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
|
|
5174
|
-
}
|
|
5175
|
-
async function deriveFromGit(inputs, opts) {
|
|
5176
|
-
const records = /* @__PURE__ */ new Map();
|
|
5177
|
-
const byRoot = groupByRoot(inputs);
|
|
5178
|
-
if (byRoot.size === 0) {
|
|
5179
|
-
return {
|
|
5180
|
-
status: {
|
|
5181
|
-
available: false,
|
|
5182
|
-
reason: "no git repository contains the documents"
|
|
5183
|
-
},
|
|
5184
|
-
records
|
|
5185
|
-
};
|
|
5186
|
-
}
|
|
5187
|
-
const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
|
|
5188
|
-
const machines = opts.machines ?? DEFAULT_MACHINES;
|
|
5189
|
-
const wantsProvenance = opts.fields?.includes(PROVENANCE_FIELD) ?? false;
|
|
5190
|
-
const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
|
|
5191
|
-
const parse = yamlOnce();
|
|
5192
|
-
const reasons = [];
|
|
5193
|
-
for (const [root, bucket] of byRoot) {
|
|
5194
|
-
const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
|
|
5195
|
-
const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
|
|
5196
|
-
if (shallow.missing) {
|
|
5197
|
-
return {
|
|
5198
|
-
status: { available: false, reason: "git is not on PATH" },
|
|
5199
|
-
records: /* @__PURE__ */ new Map()
|
|
5200
|
-
};
|
|
5201
|
-
}
|
|
5202
|
-
if (shallow.code !== 0) {
|
|
5203
|
-
reasons.push(`git could not read the repository at ${root}`);
|
|
5204
|
-
continue;
|
|
5205
|
-
}
|
|
5206
|
-
if (text(shallow).trim() === "true") {
|
|
5207
|
-
reasons.push(
|
|
5208
|
-
`this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
|
|
5209
|
-
);
|
|
5210
|
-
continue;
|
|
5211
|
-
}
|
|
5212
|
-
try {
|
|
5213
|
-
const found = await histories(
|
|
5214
|
-
run2,
|
|
5215
|
-
bucket.map((e) => e.rel),
|
|
5216
|
-
threshold
|
|
5217
|
-
);
|
|
5218
|
-
if (found === null) {
|
|
5219
|
-
reasons.push(`git could not read the history at ${root}`);
|
|
5220
|
-
continue;
|
|
5221
|
-
}
|
|
5222
|
-
await readOtherParentBlobs(run2, found);
|
|
5223
|
-
const blobs = await fetchBlobs(run2, neededBlobs(found));
|
|
5224
|
-
if (!await followTakenBodies(run2, bucket, found, blobs, threshold)) {
|
|
5225
|
-
reasons.push(`git could not read the history at ${root}`);
|
|
5226
|
-
continue;
|
|
5227
|
-
}
|
|
5228
|
-
const manifestStamps = await stampsInManifests(run2, root, bucket, found, blobs, parse);
|
|
5229
|
-
const facts = bucket.map(
|
|
5230
|
-
(entry) => judge(
|
|
5231
|
-
entry.input,
|
|
5232
|
-
root,
|
|
5233
|
-
found.get(entry.rel) ?? [],
|
|
5234
|
-
blobs,
|
|
5235
|
-
opts.now,
|
|
5236
|
-
machines,
|
|
5237
|
-
manifestStamps.get(entry.input.label)
|
|
5238
|
-
)
|
|
5239
|
-
);
|
|
5240
|
-
if (wantsProvenance) {
|
|
5241
|
-
const pages = await mapConcurrent(bucket, GIT_CONCURRENCY, async (entry) => ({
|
|
5242
|
-
entry,
|
|
5243
|
-
blame: await blameOf(run2, entry.rel, entry.input.content),
|
|
5244
|
-
history: found.get(entry.rel) ?? []
|
|
5245
|
-
}));
|
|
5246
|
-
const evidence = await commitEvidence(run2, root, pages, blobs, parse);
|
|
5247
|
-
const provenanceRun = {
|
|
5248
|
-
machines,
|
|
5249
|
-
...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
|
|
5250
|
-
};
|
|
5251
|
-
pages.forEach((page, i) => {
|
|
5252
|
-
const judged = facts[i];
|
|
5253
|
-
const commits = evidence[i];
|
|
5254
|
-
if (judged === void 0 || commits === void 0) return;
|
|
5255
|
-
const derived = provenanceFor(page.entry, page.blame, commits, provenanceRun);
|
|
5256
|
-
judged.provenance = derived.value;
|
|
5257
|
-
judged.provenanceDerivation = derived.derivation;
|
|
5258
|
-
});
|
|
5259
|
-
}
|
|
5260
|
-
bucket.forEach((entry, i) => {
|
|
5261
|
-
const judged = facts[i];
|
|
5262
|
-
if (judged !== void 0) records.set(entry.input.label, judged);
|
|
5263
|
-
});
|
|
5264
|
-
} catch (err) {
|
|
5265
|
-
if (err instanceof BlobsUnreadable) {
|
|
5266
|
-
reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
|
|
5267
|
-
continue;
|
|
5268
|
-
}
|
|
5269
|
-
if (err instanceof BlameUnreadable) {
|
|
5270
|
-
const why = err.detail === "" ? "" : `: ${err.detail}`;
|
|
5271
|
-
reasons.push(`git blame could not read ${err.rel} (${root})${why}`);
|
|
5272
|
-
continue;
|
|
5273
|
-
}
|
|
5274
|
-
if (!(err instanceof HistoryTooLarge)) throw err;
|
|
5275
|
-
reasons.push(`git history is too large to read in one pass (${root})`);
|
|
5276
|
-
continue;
|
|
5277
|
-
}
|
|
5278
|
-
}
|
|
5279
|
-
if (reasons.length > 0) {
|
|
5280
|
-
return {
|
|
5281
|
-
status: { available: false, reason: reasons.join("; ") },
|
|
5282
|
-
records: /* @__PURE__ */ new Map()
|
|
5283
|
-
};
|
|
5284
|
-
}
|
|
5285
|
-
return { status: { available: true }, records };
|
|
5286
|
-
}
|
|
5287
|
-
function groupByRoot(inputs) {
|
|
5288
|
-
const rootOf = /* @__PURE__ */ new Map();
|
|
5289
|
-
const byRoot = /* @__PURE__ */ new Map();
|
|
5290
|
-
for (const input of inputs) {
|
|
5291
|
-
const dir = dirname4(input.absPath);
|
|
5292
|
-
let root = rootOf.get(dir);
|
|
5293
|
-
if (root === void 0) {
|
|
5294
|
-
root = findGitRoot(dir);
|
|
5295
|
-
rootOf.set(dir, root);
|
|
5296
|
-
}
|
|
5297
|
-
if (root === null) continue;
|
|
5298
|
-
const rel = relative6(root, input.absPath).split(sep5).join("/");
|
|
5299
|
-
const bucket = byRoot.get(root);
|
|
5300
|
-
if (bucket) bucket.push({ input, rel });
|
|
5301
|
-
else byRoot.set(root, [{ input, rel }]);
|
|
5302
|
-
}
|
|
5303
|
-
return byRoot;
|
|
5304
|
-
}
|
|
5305
|
-
var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)%x00%P";
|
|
5306
|
-
var LOG_ARGS = [
|
|
5307
|
-
"-c",
|
|
5308
|
-
"core.quotePath=false",
|
|
5309
|
-
"log",
|
|
5310
|
-
"--raw",
|
|
5311
|
-
"--diff-merges=first-parent",
|
|
5312
|
-
"-M",
|
|
5313
|
-
"--no-abbrev",
|
|
5314
|
-
RECORD_FORMAT
|
|
5315
|
-
];
|
|
5316
|
-
function histories(run2, rels, threshold, rev = "HEAD") {
|
|
5317
|
-
return rels.length <= threshold ? perFileHistories(run2, rels, rev) : bulkHistories(run2, rels, rev);
|
|
5318
|
-
}
|
|
5319
|
-
async function perFileHistories(run2, rels, rev) {
|
|
5320
|
-
const found = /* @__PURE__ */ new Map();
|
|
5321
|
-
for (const rel of rels) {
|
|
5322
|
-
const records = await lineage(run2, rel, rev);
|
|
5323
|
-
if (records === null) {
|
|
5324
|
-
if (await isUnbornHead(run2)) return found;
|
|
5325
|
-
return null;
|
|
5326
|
-
}
|
|
5327
|
-
found.set(rel, attribute(records, [rel]).get(rel) ?? []);
|
|
5328
|
-
}
|
|
5329
|
-
return found;
|
|
5330
|
-
}
|
|
5331
|
-
async function lineage(run2, rel, rev) {
|
|
5332
|
-
const records = [];
|
|
5333
|
-
let at2 = rel;
|
|
5334
|
-
let from = rev;
|
|
5335
|
-
for (; ; ) {
|
|
5336
|
-
const out = await run2([...LOG_ARGS, from, "--", pathspec(at2)]);
|
|
5337
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5338
|
-
if (out.code !== 0) return null;
|
|
5339
|
-
let renamedFrom;
|
|
5340
|
-
for (const record of parseLog(text(out))) {
|
|
5341
|
-
const status = record.raw.find((e) => e.newPath === at2)?.status;
|
|
5342
|
-
if (status === "A" || status === "D") {
|
|
5343
|
-
const whole = await run2([...LOG_ARGS, "-1", record.sha]);
|
|
5344
|
-
if (whole.tooLarge) throw new HistoryTooLarge();
|
|
5345
|
-
if (whole.code !== 0) return null;
|
|
5346
|
-
record.raw = parseLog(text(whole))[0]?.raw ?? [];
|
|
5347
|
-
const entry = record.raw.find((e) => e.newPath === at2);
|
|
5348
|
-
if (entry !== void 0 && entry.oldPath !== at2) renamedFrom = entry.oldPath;
|
|
5349
|
-
}
|
|
5350
|
-
records.push(record);
|
|
5351
|
-
if (renamedFrom !== void 0) {
|
|
5352
|
-
from = `${record.sha}^`;
|
|
5353
|
-
break;
|
|
5354
|
-
}
|
|
5355
|
-
}
|
|
5356
|
-
if (renamedFrom === void 0) return records;
|
|
5357
|
-
at2 = renamedFrom;
|
|
5358
|
-
}
|
|
5359
|
-
}
|
|
5360
|
-
async function bulkHistories(run2, rels, rev) {
|
|
5361
|
-
const out = await run2([...LOG_ARGS, rev]);
|
|
5362
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5363
|
-
if (out.code !== 0) {
|
|
5364
|
-
if (await isUnbornHead(run2)) return /* @__PURE__ */ new Map();
|
|
5365
|
-
return null;
|
|
5366
|
-
}
|
|
5367
|
-
return attribute(parseLog(text(out)), [...rels]);
|
|
5368
|
-
}
|
|
5369
|
-
async function followTakenBodies(run2, bucket, found, blobs, threshold) {
|
|
5370
|
-
for (; ; ) {
|
|
5371
|
-
const byParent = /* @__PURE__ */ new Map();
|
|
5372
|
-
for (const entry of bucket) {
|
|
5373
|
-
const history = found.get(entry.rel) ?? [];
|
|
5374
|
-
const taken = takenMerge(entry.input.extracted.fenced, history, blobs);
|
|
5375
|
-
if (taken === void 0) continue;
|
|
5376
|
-
const group = byParent.get(taken.parent) ?? [];
|
|
5377
|
-
group.push({ rel: entry.rel, keep: history.slice(0, taken.index), path: taken.path });
|
|
5378
|
-
byParent.set(taken.parent, group);
|
|
5379
|
-
}
|
|
5380
|
-
if (byParent.size === 0) return true;
|
|
5381
|
-
const walked = /* @__PURE__ */ new Map();
|
|
5382
|
-
for (const [parent, group] of byParent) {
|
|
5383
|
-
const paths = [...new Set(group.map((g) => g.path))];
|
|
5384
|
-
const side = await histories(run2, paths, threshold, parent);
|
|
5385
|
-
if (side === null) return false;
|
|
5386
|
-
for (const g of group) {
|
|
5387
|
-
const tail = side.get(g.path) ?? [];
|
|
5388
|
-
walked.set(g.rel, tail);
|
|
5389
|
-
found.set(g.rel, [...g.keep, ...tail]);
|
|
5390
|
-
}
|
|
5391
|
-
}
|
|
5392
|
-
await readOtherParentBlobs(run2, walked);
|
|
5393
|
-
const missing = new Set([...neededBlobs(walked)].filter((sha) => !blobs.has(sha)));
|
|
5394
|
-
for (const [sha, content] of await fetchBlobs(run2, missing)) blobs.set(sha, content);
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
function takenMerge(fenced, history, blobs) {
|
|
5398
|
-
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5399
|
-
const index = history.findIndex((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
|
|
5400
|
-
const newest = history[index];
|
|
5401
|
-
if (newest === void 0) return void 0;
|
|
5402
|
-
const body = blobBody(newest.newBlob);
|
|
5403
|
-
const k = newest.otherParentBlobs.findIndex((b) => b !== null && sameBody(body, blobBody(b)));
|
|
5404
|
-
const parent = newest.otherParents[k];
|
|
5405
|
-
if (k === -1 || parent === void 0) return void 0;
|
|
5406
|
-
return { index, parent, path: newest.pathAtCommit };
|
|
5407
|
-
}
|
|
5408
|
-
function pathspec(rel) {
|
|
5409
|
-
return /[*?[\]]/.test(rel) ? `:(literal)${rel}` : rel;
|
|
5410
|
-
}
|
|
5411
|
-
async function isUnbornHead(run2) {
|
|
5412
|
-
const out = await run2(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
5413
|
-
return !out.missing && out.code !== 0;
|
|
5414
|
-
}
|
|
5415
|
-
function parseLog(text2) {
|
|
5416
|
-
const records = [];
|
|
5417
|
-
for (const chunk of text2.split("")) {
|
|
5418
|
-
if (chunk === "") continue;
|
|
5419
|
-
const nl = chunk.indexOf("\n");
|
|
5420
|
-
const header = nl === -1 ? chunk : chunk.slice(0, nl);
|
|
5421
|
-
const fields = header.split("\0");
|
|
5422
|
-
const sha = fields[0];
|
|
5423
|
-
if (sha === void 0 || sha === "") continue;
|
|
5424
|
-
const raw = [];
|
|
5425
|
-
if (nl !== -1) {
|
|
5426
|
-
for (const line of chunk.slice(nl + 1).split("\n")) {
|
|
5427
|
-
const entry = parseRawLine(line);
|
|
5428
|
-
if (entry) raw.push(entry);
|
|
5429
|
-
}
|
|
5430
|
-
}
|
|
5431
|
-
records.push({
|
|
5432
|
-
sha,
|
|
5433
|
-
authorDate: fields[1] ?? "",
|
|
5434
|
-
authorName: fields[2] ?? "",
|
|
5435
|
-
authorEmail: fields[3] ?? "",
|
|
5436
|
-
coAuthors: splitTrailers(fields[4]),
|
|
5437
|
-
reviewedBy: splitTrailers(fields[5]),
|
|
5438
|
-
generatedBy: splitTrailers(fields[6]),
|
|
5439
|
-
parents: (fields[7] ?? "").trim().split(" ").filter((p) => p !== ""),
|
|
5440
|
-
raw
|
|
5441
|
-
});
|
|
5442
|
-
}
|
|
5443
|
-
return records;
|
|
5444
|
-
}
|
|
5445
|
-
function splitTrailers(field) {
|
|
5446
|
-
if (field === void 0 || field === "") return [];
|
|
5447
|
-
return field.split("").map((s) => s.trim()).filter((s) => s !== "");
|
|
5448
|
-
}
|
|
5449
|
-
function parseRawLine(line) {
|
|
5450
|
-
if (!line.startsWith(":")) return null;
|
|
5451
|
-
const tab = line.indexOf(" ");
|
|
5452
|
-
if (tab === -1) return null;
|
|
5453
|
-
const meta = line.slice(1, tab).split(" ");
|
|
5454
|
-
const oldBlob = meta[2];
|
|
5455
|
-
const newBlob = meta[3];
|
|
5456
|
-
const status = meta[4];
|
|
5457
|
-
if (oldBlob === void 0 || newBlob === void 0 || status === void 0) {
|
|
5458
|
-
return null;
|
|
5459
|
-
}
|
|
5460
|
-
const paths = line.slice(tab + 1).split(" ").map(unquotePath);
|
|
5461
|
-
const first = paths[0];
|
|
5462
|
-
if (first === void 0) return null;
|
|
5463
|
-
const second = paths[1];
|
|
5464
|
-
const renamed = status.startsWith("R") || status.startsWith("C");
|
|
5465
|
-
return {
|
|
5466
|
-
status,
|
|
5467
|
-
oldBlob,
|
|
5468
|
-
newBlob,
|
|
5469
|
-
oldPath: first,
|
|
5470
|
-
newPath: renamed && second !== void 0 ? second : first
|
|
5471
|
-
};
|
|
5472
|
-
}
|
|
5473
|
-
function unquotePath(p) {
|
|
5474
|
-
if (p.length < 2 || !p.startsWith('"') || !p.endsWith('"')) return p;
|
|
5475
|
-
const inner = p.slice(1, -1);
|
|
5476
|
-
const bytes = [];
|
|
5477
|
-
for (let i = 0; i < inner.length; i += 1) {
|
|
5478
|
-
const ch = inner[i];
|
|
5479
|
-
if (ch !== "\\") {
|
|
5480
|
-
bytes.push(...Buffer.from(ch ?? "", "utf8"));
|
|
5481
|
-
continue;
|
|
5482
|
-
}
|
|
5483
|
-
const next = inner[i + 1];
|
|
5484
|
-
if (next === void 0) break;
|
|
5485
|
-
const octal = /^[0-7]{3}/.exec(inner.slice(i + 1));
|
|
5486
|
-
if (octal) {
|
|
5487
|
-
bytes.push(parseInt(octal[0], 8));
|
|
5488
|
-
i += 3;
|
|
5489
|
-
continue;
|
|
5490
|
-
}
|
|
5491
|
-
const simple = {
|
|
5492
|
-
a: 7,
|
|
5493
|
-
b: 8,
|
|
5494
|
-
t: 9,
|
|
5495
|
-
n: 10,
|
|
5496
|
-
v: 11,
|
|
5497
|
-
f: 12,
|
|
5498
|
-
r: 13,
|
|
5499
|
-
'"': 34,
|
|
5500
|
-
"\\": 92
|
|
5501
|
-
};
|
|
5502
|
-
const code = simple[next];
|
|
5503
|
-
if (code === void 0) bytes.push(...Buffer.from(next, "utf8"));
|
|
5504
|
-
else bytes.push(code);
|
|
5505
|
-
i += 1;
|
|
5506
|
-
}
|
|
5507
|
-
return Buffer.from(bytes).toString("utf8");
|
|
5508
|
-
}
|
|
5509
|
-
function attribute(records, wanted) {
|
|
5510
|
-
const histories2 = /* @__PURE__ */ new Map();
|
|
5511
|
-
const tracked = /* @__PURE__ */ new Map();
|
|
5512
|
-
for (const rel of wanted) {
|
|
5513
|
-
histories2.set(rel, []);
|
|
5514
|
-
tracked.set(rel, rel);
|
|
5515
|
-
}
|
|
5516
|
-
for (const record of records) {
|
|
5517
|
-
if (record.raw.length === 0) continue;
|
|
5518
|
-
const byNewPath = /* @__PURE__ */ new Map();
|
|
5519
|
-
for (const entry of record.raw) byNewPath.set(entry.newPath, entry);
|
|
5520
|
-
for (const [rel, current] of tracked) {
|
|
5521
|
-
const entry = byNewPath.get(current);
|
|
5522
|
-
if (!entry) continue;
|
|
5523
|
-
histories2.get(rel)?.push({
|
|
5524
|
-
sha: record.sha,
|
|
5525
|
-
authorDate: record.authorDate,
|
|
5526
|
-
authorName: record.authorName,
|
|
5527
|
-
authorEmail: record.authorEmail,
|
|
5528
|
-
coAuthors: record.coAuthors,
|
|
5529
|
-
reviewedBy: record.reviewedBy,
|
|
5530
|
-
generatedBy: record.generatedBy,
|
|
5531
|
-
oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
|
|
5532
|
-
newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
|
|
5533
|
-
pathAtCommit: entry.newPath,
|
|
5534
|
-
otherParents: record.parents.slice(1),
|
|
5535
|
-
otherParentBlobs: []
|
|
5536
|
-
});
|
|
5537
|
-
if (entry.oldPath !== entry.newPath) tracked.set(rel, entry.oldPath);
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
|
-
return histories2;
|
|
5541
|
-
}
|
|
5542
|
-
function neededBlobs(histories2) {
|
|
5543
|
-
const shas = /* @__PURE__ */ new Set();
|
|
5544
|
-
for (const history of histories2.values()) {
|
|
5545
|
-
for (const h of history) {
|
|
5546
|
-
if (h.oldBlob !== null) shas.add(h.oldBlob);
|
|
5547
|
-
if (h.newBlob !== null) shas.add(h.newBlob);
|
|
5548
|
-
for (const b of h.otherParentBlobs) if (b !== null) shas.add(b);
|
|
5549
|
-
}
|
|
5550
|
-
}
|
|
5551
|
-
return shas;
|
|
5552
|
-
}
|
|
5553
|
-
async function readOtherParentBlobs(run2, histories2) {
|
|
5554
|
-
const merges = [];
|
|
5555
|
-
const specs = [];
|
|
5556
|
-
for (const history of histories2.values()) {
|
|
5557
|
-
for (const h of history) {
|
|
5558
|
-
if (h.otherParents.length === 0) continue;
|
|
5559
|
-
merges.push(h);
|
|
5560
|
-
for (const parent of h.otherParents) specs.push(`${parent}:${h.pathAtCommit}`);
|
|
5561
|
-
}
|
|
5562
|
-
}
|
|
5563
|
-
if (specs.length === 0) return;
|
|
5564
|
-
const out = await run2(["cat-file", "--batch-check"], `${specs.join("\n")}
|
|
5565
|
-
`);
|
|
5566
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5567
|
-
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5568
|
-
const lines = text(out).split("\n");
|
|
5569
|
-
let i = 0;
|
|
5570
|
-
for (const h of merges) {
|
|
5571
|
-
h.otherParentBlobs = h.otherParents.map(() => blobOfCheckLine(lines[i++]));
|
|
5572
|
-
}
|
|
5573
|
-
}
|
|
5574
|
-
function blobOfCheckLine(line) {
|
|
5575
|
-
const [sha, kind] = (line ?? "").split(" ");
|
|
5576
|
-
if (sha === void 0 || kind !== "blob" || !/^[0-9a-f]{40,64}$/.test(sha)) return null;
|
|
5577
|
-
return sha;
|
|
5578
|
-
}
|
|
5579
|
-
async function fetchBlobs(run2, shas) {
|
|
5580
|
-
const blobs = /* @__PURE__ */ new Map();
|
|
5581
|
-
if (shas.size === 0) return blobs;
|
|
5582
|
-
const out = await run2(["cat-file", "--batch"], `${[...shas].join("\n")}
|
|
5583
|
-
`);
|
|
5584
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5585
|
-
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5586
|
-
const buf = out.raw;
|
|
5587
|
-
let cursor = 0;
|
|
5588
|
-
while (cursor < buf.length) {
|
|
5589
|
-
const nl = buf.indexOf(10, cursor);
|
|
5590
|
-
if (nl === -1) break;
|
|
5591
|
-
const header = buf.toString("utf8", cursor, nl).split(" ");
|
|
5592
|
-
cursor = nl + 1;
|
|
5593
|
-
const sha = header[0];
|
|
5594
|
-
const kind = header[1];
|
|
5595
|
-
const size = Number(header[2]);
|
|
5596
|
-
if (sha === void 0 || kind !== "blob" || !Number.isFinite(size)) {
|
|
5597
|
-
continue;
|
|
5598
|
-
}
|
|
5599
|
-
blobs.set(sha, buf.toString("utf8", cursor, cursor + size));
|
|
5600
|
-
cursor += size + 1;
|
|
5601
|
-
}
|
|
5602
|
-
return blobs;
|
|
5603
|
-
}
|
|
5604
|
-
var git = (value, evidence) => ({
|
|
5605
|
-
value,
|
|
5606
|
-
source: "git",
|
|
5607
|
-
evidence
|
|
5608
|
-
});
|
|
5609
|
-
function factCommits(input, history, blobs) {
|
|
5610
|
-
const fenced = input.extracted.fenced;
|
|
5611
|
-
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5612
|
-
const bodyChanging = [...history].reverse().filter((h) => {
|
|
5613
|
-
const body = blobBody(h.newBlob);
|
|
5614
|
-
if (sameBody(body, blobBody(h.oldBlob))) return false;
|
|
5615
|
-
return !h.otherParentBlobs.some((b) => b !== null && sameBody(body, blobBody(b)));
|
|
5616
|
-
});
|
|
5617
|
-
return {
|
|
5618
|
-
bodyChanging,
|
|
5619
|
-
oldest: history[history.length - 1],
|
|
5620
|
-
newestBody: bodyChanging[bodyChanging.length - 1]
|
|
5621
|
-
};
|
|
5622
|
-
}
|
|
5623
|
-
async function stampsInManifests(run2, root, bucket, histories2, blobs, parse) {
|
|
5624
|
-
const reads = [];
|
|
5625
|
-
for (const entry of bucket) {
|
|
5626
|
-
const refs = entry.input.stampManifests;
|
|
5627
|
-
if (refs === void 0) continue;
|
|
5628
|
-
const { oldest, newestBody } = factCommits(entry.input, histories2.get(entry.rel) ?? [], blobs);
|
|
5629
|
-
for (const field of STAMPED_DATE_FIELDS) {
|
|
5630
|
-
const ref = refs[field];
|
|
5631
|
-
const at2 = field === "created" ? oldest : newestBody;
|
|
5632
|
-
if (ref === void 0 || at2 === void 0) continue;
|
|
5633
|
-
const rel = insideRoot(root, ref.absPath);
|
|
5634
|
-
if (rel === void 0) continue;
|
|
5635
|
-
const key = ref.join === "path" ? entryAt(ref.entry, entry.rel, at2.pathAtCommit) : ref.entry;
|
|
5636
|
-
const revs = field === "created" ? [at2.sha] : [at2.sha, `${at2.sha}^`];
|
|
5637
|
-
for (const rev of revs) {
|
|
5638
|
-
reads.push({ label: entry.input.label, field, rev, spec: `${rev}:${rel}`, key, join: ref.join });
|
|
5639
|
-
}
|
|
5640
|
-
}
|
|
5641
|
-
}
|
|
5642
|
-
const out = /* @__PURE__ */ new Map();
|
|
5643
|
-
if (reads.length === 0) return out;
|
|
5644
|
-
const fetched = await fetchSpecs(run2, [...new Set(reads.map((r) => r.spec))]);
|
|
5645
|
-
const values = /* @__PURE__ */ new Map();
|
|
5646
|
-
const keyOf = (label, field, rev) => `${label}\0${field}\0${rev}`;
|
|
5647
|
-
for (const r of reads) {
|
|
5648
|
-
values.set(keyOf(r.label, r.field, r.rev), manifestValue(fetched.get(r.spec), r.key, r.join, r.field, parse));
|
|
5649
|
-
}
|
|
5650
|
-
for (const label of new Set(reads.map((r) => r.label))) {
|
|
5651
|
-
out.set(label, (field, rev) => values.get(keyOf(label, field, rev)));
|
|
5652
|
-
}
|
|
5653
|
-
return out;
|
|
5654
|
-
}
|
|
5655
|
-
function judge(input, root, history, blobs, now, machines, manifestStamp2) {
|
|
5656
|
-
const fenced = input.extracted.fenced;
|
|
5657
|
-
const extractor = extractorFor(input);
|
|
5658
|
-
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5659
|
-
const stampsOf = (sha) => {
|
|
5660
|
-
if (sha === null) return {};
|
|
5661
|
-
const content = blobs.get(sha);
|
|
5662
|
-
return content === void 0 ? {} : extractStamps(extractor, content, input.absPath);
|
|
5663
|
-
};
|
|
5664
|
-
const stampAt = (field, h, parent) => {
|
|
5665
|
-
const kept = manifestStamp2?.(field, parent ? `${h.sha}^` : h.sha);
|
|
5666
|
-
if (kept !== void 0 && kept !== null) return kept;
|
|
5667
|
-
return stampsOf(parent ? h.oldBlob : h.newBlob)[field];
|
|
5668
|
-
};
|
|
5669
|
-
const { bodyChanging, oldest, newestBody } = factCommits(input, history, blobs);
|
|
5670
|
-
const head = history[0];
|
|
5671
|
-
const headBlob = head === void 0 ? null : head.newBlob;
|
|
5672
|
-
const created = oldest === void 0 ? null : createdOf(oldest, stampAt("created", oldest, false));
|
|
5673
|
-
const workingChanged = !sameBody(
|
|
5674
|
-
bodyOf(input.content, fenced),
|
|
5675
|
-
headBlob === null ? null : blobBody(headBlob)
|
|
5676
|
-
);
|
|
5677
|
-
let lastUpdated;
|
|
5678
|
-
let lastBodyCommit;
|
|
5679
|
-
if (workingChanged) {
|
|
5680
|
-
lastBodyCommit = null;
|
|
5681
|
-
const working = input.extracted.data["last-updated"];
|
|
5682
|
-
const committed = stampsOf(headBlob)["last-updated"];
|
|
5683
|
-
lastUpdated = working !== void 0 && working !== null && !sameValue(working, committed) ? git(working, "stamped in working tree") : git(utcDate2(now()), "uncommitted body change");
|
|
5684
|
-
} else if (newestBody === void 0) {
|
|
5685
|
-
lastBodyCommit = null;
|
|
5686
|
-
lastUpdated = null;
|
|
5687
|
-
} else {
|
|
5688
|
-
lastBodyCommit = newestBody.sha;
|
|
5689
|
-
lastUpdated = lastUpdatedOf(
|
|
5690
|
-
newestBody,
|
|
5691
|
-
stampAt("last-updated", newestBody, false),
|
|
5692
|
-
stampAt("last-updated", newestBody, true)
|
|
5693
|
-
);
|
|
5694
|
-
}
|
|
5695
|
-
const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
|
|
5696
|
-
let reviewedBy = null;
|
|
5697
|
-
let lastReviewed = null;
|
|
5698
|
-
if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
|
|
5699
|
-
const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
|
|
5700
|
-
reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
|
|
5701
|
-
lastReviewed = git(datePart(newestBody.authorDate), evidence);
|
|
5702
|
-
}
|
|
5703
|
-
return {
|
|
5704
|
-
created,
|
|
5705
|
-
"last-updated": lastUpdated,
|
|
5706
|
-
authors,
|
|
5707
|
-
"reviewed-by": reviewedBy,
|
|
5708
|
-
"last-reviewed": lastReviewed,
|
|
5709
|
-
provenance: null,
|
|
5710
|
-
lastBodyCommit,
|
|
5711
|
-
root
|
|
5712
|
-
};
|
|
5713
|
-
}
|
|
5714
|
-
function createdOf(oldest, stamp) {
|
|
5715
|
-
if (stamp !== void 0 && stamp !== null) {
|
|
5716
|
-
return git(stamp, `stamped in ${short2(oldest.sha)}`);
|
|
5717
|
-
}
|
|
5718
|
-
const date = datePart(oldest.authorDate);
|
|
5719
|
-
return git(date, `added in ${short2(oldest.sha)} (${date})`);
|
|
5720
|
-
}
|
|
5721
|
-
function lastUpdatedOf(commit, stamp, before) {
|
|
5722
|
-
if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before)) {
|
|
5723
|
-
return git(stamp, `stamped in ${short2(commit.sha)}`);
|
|
5724
|
-
}
|
|
5725
|
-
const date = datePart(commit.authorDate);
|
|
5726
|
-
return git(date, `body changed in ${short2(commit.sha)} (${date})`);
|
|
5727
|
-
}
|
|
5728
|
-
function authorsOf(bodyChanging, machines) {
|
|
5729
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5730
|
-
const names = [];
|
|
5731
|
-
const add = (name, email) => {
|
|
5732
|
-
if (name === "") return;
|
|
5733
|
-
const identity = email === null || email === "" ? name : `${name} <${email}>`;
|
|
5734
|
-
if (machineIdentity(identity, machines) !== void 0) return;
|
|
5735
|
-
const key = email === null || email === "" ? `name:${name.toLowerCase()}` : email.toLowerCase();
|
|
5736
|
-
if (seen.has(key)) return;
|
|
5737
|
-
seen.add(key);
|
|
5738
|
-
names.push(name);
|
|
5739
|
-
};
|
|
5740
|
-
for (const h of bodyChanging) {
|
|
5741
|
-
add(h.authorName, h.authorEmail);
|
|
5742
|
-
for (const co of h.coAuthors) add(nameOf(co), emailOf(co));
|
|
5743
|
-
}
|
|
5744
|
-
const n = bodyChanging.length;
|
|
5745
|
-
return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
|
|
5746
|
-
}
|
|
5747
|
-
function provenanceFenced(extracted) {
|
|
5748
|
-
return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
|
|
5749
|
-
}
|
|
5750
|
-
var OUTSIDE_RANGE = "outside-the-attributed-range";
|
|
5751
|
-
var ATTRIBUTED = ":attributed";
|
|
5752
|
-
function provenanceFor(entry, blame, commits, opts) {
|
|
5753
|
-
const { input } = entry;
|
|
5754
|
-
const fenced = provenanceFenced(input.extracted);
|
|
5755
|
-
const base = { content: input.content, commits, machines: opts.machines, fenced };
|
|
5756
|
-
let derivation;
|
|
5757
|
-
const attribution = input.attribution;
|
|
5758
|
-
if (attribution !== void 0) {
|
|
5759
|
-
const page = readProvenancePage(input.content, { fenced });
|
|
5760
|
-
const { bodyLine } = page;
|
|
5761
|
-
const ranges = attribution.targets.flatMap((target) => {
|
|
5762
|
-
const entry2 = attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy, page });
|
|
5763
|
-
const span = entry2 === void 0 ? void 0 : parseLines(entry2.lines);
|
|
5764
|
-
return span === void 0 ? [] : [toFileLines(span, bodyLine)];
|
|
5765
|
-
});
|
|
5766
|
-
const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
|
|
5767
|
-
const attributed = new Map(commits);
|
|
5768
|
-
const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
|
|
5769
|
-
attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
|
|
5770
|
-
const rekeyed = blame.map((line) => {
|
|
5771
|
-
if (inRange(line)) {
|
|
5772
|
-
if (line.uncommitted) return line;
|
|
5773
|
-
const sha = `${line.sha}${ATTRIBUTED}`;
|
|
5774
|
-
attributed.set(sha, { sha, trailers });
|
|
5775
|
-
return { ...line, sha };
|
|
5776
|
-
}
|
|
5777
|
-
return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
|
|
5778
|
-
});
|
|
5779
|
-
derivation = deriveProvenance({
|
|
5780
|
-
...base,
|
|
5781
|
-
commits: attributed,
|
|
5782
|
-
generatedBy: attribution.generatedBy,
|
|
5783
|
-
blame: rekeyed
|
|
5784
|
-
});
|
|
5785
|
-
} else {
|
|
5786
|
-
derivation = deriveProvenance({
|
|
5787
|
-
...base,
|
|
5788
|
-
blame,
|
|
5789
|
-
...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
|
|
5790
|
-
});
|
|
5791
|
-
}
|
|
5792
|
-
const entries = derivation.derived.map((d) => d.entry);
|
|
5793
|
-
return {
|
|
5794
|
-
value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
|
|
5795
|
-
derivation
|
|
5796
|
-
};
|
|
5797
|
-
}
|
|
5798
|
-
function provenanceEvidence(derivation) {
|
|
5799
|
-
const shas = [];
|
|
5800
|
-
let uncommitted = false;
|
|
5801
|
-
for (const range of derivation.derived) {
|
|
5802
|
-
for (let n = range.span.start; n <= range.span.end; n++) {
|
|
5803
|
-
const evidence = derivation.evidenceByLine.get(n);
|
|
5804
|
-
if (evidence?.machine === void 0) continue;
|
|
5805
|
-
if (evidence.rule === 1) uncommitted = true;
|
|
5806
|
-
else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
|
|
5807
|
-
}
|
|
5808
|
-
}
|
|
5809
|
-
const first = shas[0];
|
|
5810
|
-
if (first === void 0) return "uncommitted";
|
|
5811
|
-
const parts = [`blame ${short2(first)}`];
|
|
5812
|
-
if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
|
|
5813
|
-
if (uncommitted) parts.push("uncommitted");
|
|
5814
|
-
return parts.join(", ");
|
|
5815
|
-
}
|
|
5816
|
-
async function blameOf(run2, rel, content) {
|
|
5817
|
-
const out = await run2([
|
|
5818
|
-
"-c",
|
|
5819
|
-
"core.quotePath=false",
|
|
5820
|
-
"blame",
|
|
5821
|
-
"--no-ignore-revs-file",
|
|
5822
|
-
"--line-porcelain",
|
|
5823
|
-
"--",
|
|
5824
|
-
rel
|
|
5825
|
-
]);
|
|
5826
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5827
|
-
if (out.code !== 0) {
|
|
5828
|
-
const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
|
|
5829
|
-
if (inHead.code !== 0) {
|
|
5830
|
-
return splitLines(content).map((line, i) => ({
|
|
5831
|
-
sha: ZERO_SHA,
|
|
5832
|
-
origLine: i + 1,
|
|
5833
|
-
finalLine: i + 1,
|
|
5834
|
-
author: "Not Committed Yet",
|
|
5835
|
-
authorMail: "not.committed.yet",
|
|
5836
|
-
filename: rel,
|
|
5837
|
-
boundary: false,
|
|
5838
|
-
uncommitted: true,
|
|
5839
|
-
content: line
|
|
5840
|
-
}));
|
|
5841
|
-
}
|
|
5842
|
-
throw new BlameUnreadable(rel, out.stderr.trim());
|
|
5843
|
-
}
|
|
5844
|
-
return parseLinePorcelain(text(out));
|
|
5845
|
-
}
|
|
5846
|
-
async function commitEvidence(run2, root, pages, blobs, parse) {
|
|
5847
|
-
const plans = pages.map(({ entry, blame, history }) => {
|
|
5848
|
-
const pathAt = /* @__PURE__ */ new Map();
|
|
5849
|
-
for (const line of blame) {
|
|
5850
|
-
if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
|
|
5851
|
-
}
|
|
5852
|
-
const trailers = /* @__PURE__ */ new Map();
|
|
5853
|
-
const pageBlob = /* @__PURE__ */ new Map();
|
|
5854
|
-
for (const h of history) {
|
|
5855
|
-
trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
|
|
5856
|
-
const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
|
|
5857
|
-
if (blob !== void 0) pageBlob.set(h.sha, blob);
|
|
5858
|
-
}
|
|
5859
|
-
const manifest = entry.input.provenanceManifest;
|
|
5860
|
-
const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
|
|
5861
|
-
return { entry, pathAt, trailers, pageBlob, manifest, manifestRel };
|
|
5862
|
-
});
|
|
5863
|
-
const unlogged = /* @__PURE__ */ new Set();
|
|
5864
|
-
const specs = /* @__PURE__ */ new Set();
|
|
5865
|
-
for (const { pathAt, trailers, pageBlob, manifestRel } of plans) {
|
|
5866
|
-
for (const [sha, path] of pathAt) {
|
|
5867
|
-
if (!trailers.has(sha)) unlogged.add(sha);
|
|
5868
|
-
if (!pageBlob.has(sha)) specs.add(`${sha}:${path}`);
|
|
5869
|
-
if (manifestRel !== void 0) specs.add(`${sha}:${manifestRel}`);
|
|
5870
|
-
}
|
|
5871
|
-
}
|
|
5872
|
-
const logged = /* @__PURE__ */ new Map();
|
|
5873
|
-
if (unlogged.size > 0) {
|
|
5874
|
-
const out = await run2(["log", "--no-walk=unsorted", "--stdin", RECORD_FORMAT], `${[...unlogged].join("\n")}
|
|
5875
|
-
`);
|
|
5876
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5877
|
-
if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
|
|
5878
|
-
for (const record of parseLog(text(out))) {
|
|
5879
|
-
logged.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
|
|
5880
|
-
}
|
|
5881
|
-
}
|
|
5882
|
-
const fetched = await fetchSpecs(run2, [...specs]);
|
|
5883
|
-
return plans.map(({ entry, pathAt, trailers, pageBlob, manifest, manifestRel }) => {
|
|
5884
|
-
const commits = /* @__PURE__ */ new Map();
|
|
5885
|
-
for (const [sha, path] of pathAt) {
|
|
5886
|
-
const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${path}`);
|
|
5887
|
-
commits.set(sha, {
|
|
5888
|
-
sha,
|
|
5889
|
-
trailers: trailers.get(sha) ?? logged.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
|
|
5890
|
-
...blob !== void 0 ? { blob } : {},
|
|
5891
|
-
...manifest !== void 0 ? {
|
|
5892
|
-
stamp: manifestRel === void 0 ? [] : manifestStamp(
|
|
5893
|
-
fetched.get(`${sha}:${manifestRel}`),
|
|
5894
|
-
manifest.join === "path" ? entryAt(manifest.entry, entry.rel, path) : manifest.entry,
|
|
5895
|
-
manifest.join,
|
|
5896
|
-
parse
|
|
5897
|
-
)
|
|
5898
|
-
} : {}
|
|
5899
|
-
});
|
|
5900
|
-
}
|
|
5901
|
-
return commits;
|
|
5902
|
-
});
|
|
5903
|
-
}
|
|
5904
|
-
function insideRoot(root, abs) {
|
|
5905
|
-
const rel = relative6(root, abs);
|
|
5906
|
-
if (rel === "" || rel.startsWith("..") || isAbsolute4(rel)) return void 0;
|
|
5907
|
-
return rel.split(sep5).join("/");
|
|
5908
|
-
}
|
|
5909
|
-
function entryAt(entry, rel, relAtCommit) {
|
|
5910
|
-
if (relAtCommit === rel) return entry;
|
|
5911
|
-
const fromHere = posix4.relative(posix4.dirname(rel), relAtCommit);
|
|
5912
|
-
return posix4.join(posix4.dirname(entry), fromHere);
|
|
5913
|
-
}
|
|
5914
|
-
function manifestStamp(text2, key, join5, parse) {
|
|
5915
|
-
return provenanceEntries(manifestValue(text2, key, join5, PROVENANCE_FIELD, parse));
|
|
5916
|
-
}
|
|
5917
|
-
function yamlOnce() {
|
|
5918
|
-
const parsed = /* @__PURE__ */ new Map();
|
|
5919
|
-
return (text2) => {
|
|
5920
|
-
if (parsed.has(text2)) return parsed.get(text2);
|
|
5921
|
-
let data;
|
|
5922
|
-
try {
|
|
5923
|
-
data = parseYaml3(text2);
|
|
5924
|
-
} catch {
|
|
5925
|
-
data = void 0;
|
|
5926
|
-
}
|
|
5927
|
-
parsed.set(text2, data);
|
|
5928
|
-
return data;
|
|
5929
|
-
};
|
|
5930
|
-
}
|
|
5931
|
-
function manifestValue(text2, key, join5, field, parse) {
|
|
5932
|
-
if (text2 === void 0) return void 0;
|
|
5933
|
-
const data = parse(text2);
|
|
5934
|
-
if (!isRecord6(data)) return void 0;
|
|
5935
|
-
for (const [candidate, value] of Object.entries(data)) {
|
|
5936
|
-
const same = join5 === "path" ? posix4.normalize(candidate) === posix4.normalize(key) : candidate === key;
|
|
5937
|
-
if (same && isRecord6(value)) return value[field];
|
|
5938
|
-
}
|
|
5939
|
-
return void 0;
|
|
5940
|
-
}
|
|
5941
|
-
function isRecord6(x) {
|
|
5942
|
-
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
5943
|
-
}
|
|
5944
|
-
async function fetchSpecs(run2, specs) {
|
|
5945
|
-
const found = /* @__PURE__ */ new Map();
|
|
5946
|
-
if (specs.length === 0) return found;
|
|
5947
|
-
const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
|
|
5948
|
-
`);
|
|
5949
|
-
if (out.tooLarge && specs.length > 1) {
|
|
5950
|
-
const half = Math.ceil(specs.length / 2);
|
|
5951
|
-
const first = await fetchSpecs(run2, specs.slice(0, half));
|
|
5952
|
-
const rest = await fetchSpecs(run2, specs.slice(half));
|
|
5953
|
-
return new Map([...first, ...rest]);
|
|
5954
|
-
}
|
|
5955
|
-
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5956
|
-
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5957
|
-
for (const [spec, content] of parseSpecBatch(out.raw, specs)) found.set(spec, content);
|
|
5958
|
-
return found;
|
|
5959
|
-
}
|
|
5960
|
-
function parseSpecBatch(buf, specs) {
|
|
5961
|
-
const found = /* @__PURE__ */ new Map();
|
|
5962
|
-
let cursor = 0;
|
|
5963
|
-
for (const spec of specs) {
|
|
5964
|
-
const nl = buf.indexOf(10, cursor);
|
|
5965
|
-
if (nl === -1) throw new BlobsUnreadable(`the answer ends before ${spec}`);
|
|
5966
|
-
const header = buf.toString("utf8", cursor, nl);
|
|
5967
|
-
cursor = nl + 1;
|
|
5968
|
-
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
|
|
5969
|
-
const parts = header.split(" ");
|
|
5970
|
-
const size = Number(parts[2]);
|
|
5971
|
-
if (parts.length !== 3 || !Number.isInteger(size) || size < 0) {
|
|
5972
|
-
throw new BlobsUnreadable(`unreadable header for ${spec}: ${header}`);
|
|
5973
|
-
}
|
|
5974
|
-
if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
|
|
5975
|
-
cursor += size + 1;
|
|
5976
|
-
}
|
|
5977
|
-
return found;
|
|
5978
|
-
}
|
|
5979
|
-
function extractorFor(input) {
|
|
5980
|
-
return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
|
|
5981
|
-
}
|
|
5982
|
-
function extractStamps(extractor, content, filePath) {
|
|
5983
|
-
if (!extractor) return {};
|
|
5984
|
-
try {
|
|
5985
|
-
return extractor.extract(content, filePath).data;
|
|
5986
|
-
} catch {
|
|
5987
|
-
return {};
|
|
5988
|
-
}
|
|
5989
|
-
}
|
|
5990
|
-
function sameBody(a, b) {
|
|
5991
|
-
if (a === null || b === null) return a === b;
|
|
5992
|
-
return a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
|
|
5993
|
-
}
|
|
5994
|
-
function sameValue(a, b) {
|
|
5995
|
-
return toJsonText(a) === toJsonText(b);
|
|
5996
|
-
}
|
|
5997
|
-
var short2 = (sha) => sha.slice(0, 7);
|
|
5998
|
-
var datePart = (iso) => {
|
|
5999
|
-
const d = new Date(iso);
|
|
6000
|
-
return Number.isNaN(d.getTime()) ? "" : utcDate2(d);
|
|
6001
|
-
};
|
|
6002
|
-
function utcDate2(d) {
|
|
6003
|
-
return d.toISOString().slice(0, 10);
|
|
6004
|
-
}
|
|
6005
|
-
function nameOf(trailer) {
|
|
6006
|
-
const lt = trailer.lastIndexOf("<");
|
|
6007
|
-
return (lt === -1 ? trailer : trailer.slice(0, lt)).trim();
|
|
6008
|
-
}
|
|
6009
|
-
function emailOf(trailer) {
|
|
6010
|
-
const m = /<([^>]*)>\s*$/.exec(trailer);
|
|
6011
|
-
return m?.[1]?.trim() ?? null;
|
|
6012
|
-
}
|
|
6013
|
-
var MAX_STDERR_BYTES = 16 * 1024;
|
|
6014
|
-
var GIT_CONCURRENCY = Math.min(8, availableParallelism());
|
|
6015
|
-
function text(out) {
|
|
6016
|
-
try {
|
|
6017
|
-
return out.raw.toString("utf8");
|
|
6018
|
-
} catch {
|
|
6019
|
-
throw new HistoryTooLarge();
|
|
6020
|
-
}
|
|
6021
|
-
}
|
|
6022
|
-
function runGit(args, cwd, stdin, maxBytes) {
|
|
6023
|
-
return new Promise((settle2, reject) => {
|
|
6024
|
-
let done = false;
|
|
6025
|
-
const finish = (out) => {
|
|
6026
|
-
if (done) return;
|
|
6027
|
-
done = true;
|
|
6028
|
-
settle2(out);
|
|
6029
|
-
};
|
|
6030
|
-
const fail = (err) => {
|
|
6031
|
-
if (done) return;
|
|
6032
|
-
done = true;
|
|
6033
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6034
|
-
};
|
|
6035
|
-
const failed = (missing) => ({
|
|
6036
|
-
code: null,
|
|
6037
|
-
raw: Buffer.alloc(0),
|
|
6038
|
-
missing,
|
|
6039
|
-
tooLarge: false,
|
|
6040
|
-
stderr: ""
|
|
6041
|
-
});
|
|
6042
|
-
let child;
|
|
6043
|
-
try {
|
|
6044
|
-
child = spawn2("git", args, { cwd, windowsHide: true });
|
|
6045
|
-
} catch {
|
|
6046
|
-
finish(failed(true));
|
|
6047
|
-
return;
|
|
6048
|
-
}
|
|
6049
|
-
let chunks = [];
|
|
6050
|
-
let total = 0;
|
|
6051
|
-
let tooLarge = false;
|
|
6052
|
-
child.stdout.on("data", (chunk) => {
|
|
6053
|
-
if (tooLarge) return;
|
|
6054
|
-
total += chunk.length;
|
|
6055
|
-
if (total > maxBytes) {
|
|
6056
|
-
tooLarge = true;
|
|
6057
|
-
chunks = [];
|
|
6058
|
-
child.kill();
|
|
6059
|
-
return;
|
|
6060
|
-
}
|
|
6061
|
-
chunks.push(chunk);
|
|
6062
|
-
});
|
|
6063
|
-
const errChunks = [];
|
|
6064
|
-
let errTotal = 0;
|
|
6065
|
-
child.stderr.on("data", (chunk) => {
|
|
6066
|
-
if (errTotal >= MAX_STDERR_BYTES) return;
|
|
6067
|
-
const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
|
|
6068
|
-
errTotal += kept.length;
|
|
6069
|
-
errChunks.push(kept);
|
|
6070
|
-
});
|
|
6071
|
-
child.on("error", (err) => {
|
|
6072
|
-
finish(failed(err.code === "ENOENT"));
|
|
6073
|
-
});
|
|
6074
|
-
child.stdin.on("error", () => {
|
|
6075
|
-
});
|
|
6076
|
-
child.on("close", (code) => {
|
|
6077
|
-
try {
|
|
6078
|
-
const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
|
|
6079
|
-
const stderr = Buffer.concat(errChunks).toString("utf8");
|
|
6080
|
-
finish({ code, raw, missing: false, tooLarge, stderr });
|
|
6081
|
-
} catch (err) {
|
|
6082
|
-
fail(err);
|
|
6083
|
-
}
|
|
6084
|
-
});
|
|
6085
|
-
if (stdin === void 0) child.stdin.end();
|
|
6086
|
-
else child.stdin.end(stdin);
|
|
6087
|
-
});
|
|
6088
|
-
}
|
|
6089
|
-
|
|
6090
|
-
// src/meta/core/derive/index.ts
|
|
6091
|
-
var FIELD_SOURCES = {
|
|
6092
|
-
created: ["git"],
|
|
6093
|
-
"last-updated": ["git"],
|
|
6094
|
-
authors: ["git"],
|
|
6095
|
-
owner: ["codeowners"],
|
|
6096
|
-
"reviewed-by": ["github", "gitlab", "git"],
|
|
6097
|
-
"last-reviewed": ["github", "gitlab", "git"],
|
|
6098
|
-
provenance: ["git"]
|
|
6099
|
-
};
|
|
6100
|
-
var REVIEW_SOURCES = ["github", "gitlab"];
|
|
6101
|
-
var HOST_NAME = {
|
|
6102
|
-
github: "GitHub",
|
|
6103
|
-
gitlab: "GitLab"
|
|
6104
|
-
};
|
|
6105
|
-
function sourcesFor(field, commands) {
|
|
6106
|
-
if (isBuiltinField(field)) return FIELD_SOURCES[field];
|
|
6107
|
-
if (commands !== void 0 && Object.hasOwn(commands, field)) return ["command"];
|
|
6108
|
-
return [];
|
|
6109
|
-
}
|
|
6110
|
-
function consultedSources(fields, sources, commands) {
|
|
6111
|
-
const needed = /* @__PURE__ */ new Set();
|
|
6112
|
-
for (const field of fields) {
|
|
6113
|
-
for (const s of sourcesFor(field, commands)) needed.add(s);
|
|
6114
|
-
}
|
|
6115
|
-
return sources.filter((s) => needed.has(s));
|
|
6116
|
-
}
|
|
6117
|
-
function assertSourcesAvailable(sources, hint) {
|
|
6118
|
-
for (const name of Object.keys(sources)) {
|
|
6119
|
-
const status = sources[name];
|
|
6120
|
-
if (status === void 0 || status.available) continue;
|
|
6121
|
-
const reason = status.reason ?? "it could not answer";
|
|
6122
|
-
throw new DocmetaError(
|
|
6123
|
-
`${name} source unavailable: ${reason}${hint === void 0 ? "" : `; ${hint}`}`
|
|
6124
|
-
);
|
|
6125
|
-
}
|
|
6126
|
-
}
|
|
6127
|
-
async function deriveMetadata(inputs, ctx) {
|
|
6128
|
-
const consulted = new Set(consultedSources(ctx.fields, ctx.sources, ctx.commands));
|
|
6129
|
-
const sources = {};
|
|
6130
|
-
let git2 = /* @__PURE__ */ new Map();
|
|
6131
|
-
if (consulted.has("git")) {
|
|
6132
|
-
const result = await deriveFromGit(inputs, {
|
|
6133
|
-
cwd: ctx.cwd,
|
|
6134
|
-
now: ctx.now,
|
|
6135
|
-
fields: ctx.fields,
|
|
6136
|
-
...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
|
|
6137
|
-
...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
|
|
6138
|
-
});
|
|
6139
|
-
sources.git = result.status;
|
|
6140
|
-
git2 = result.records;
|
|
6141
|
-
}
|
|
6142
|
-
const provenance = /* @__PURE__ */ new Map();
|
|
6143
|
-
for (const [label, facts] of git2) {
|
|
6144
|
-
if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
|
|
6145
|
-
}
|
|
6146
|
-
const rootCache = /* @__PURE__ */ new Map();
|
|
6147
|
-
const rootOf = (absPath) => {
|
|
6148
|
-
const dir = dirname5(absPath);
|
|
6149
|
-
let root = rootCache.get(dir);
|
|
6150
|
-
if (root === void 0) {
|
|
6151
|
-
root = findGitRoot(dir);
|
|
6152
|
-
rootCache.set(dir, root);
|
|
6153
|
-
}
|
|
6154
|
-
return root;
|
|
6155
|
-
};
|
|
6156
|
-
const rootFor = (input) => git2.get(input.label)?.root ?? rootOf(input.absPath);
|
|
6157
|
-
let owners = /* @__PURE__ */ new Map();
|
|
6158
|
-
if (consulted.has("codeowners")) {
|
|
6159
|
-
const result = await deriveFromCodeowners(inputs, {
|
|
6160
|
-
explicit: ctx.codeowners,
|
|
6161
|
-
configDir: ctx.configDir,
|
|
6162
|
-
rootOf
|
|
6163
|
-
});
|
|
6164
|
-
sources.codeowners = result.status;
|
|
6165
|
-
owners = result.records;
|
|
6166
|
-
}
|
|
6167
|
-
const requested = REVIEW_SOURCES.filter((s) => consulted.has(s));
|
|
6168
|
-
let reviews = /* @__PURE__ */ new Map();
|
|
6169
|
-
if (requested.length > 0) {
|
|
6170
|
-
if (!consulted.has("git")) {
|
|
6171
|
-
for (const s of requested) {
|
|
6172
|
-
sources[s] = {
|
|
6173
|
-
available: false,
|
|
6174
|
-
reason: `the ${s} source needs the git source to find each document's commits; add git to sources`
|
|
6175
|
-
};
|
|
6176
|
-
}
|
|
6177
|
-
} else if (sources.git?.available === false) {
|
|
6178
|
-
for (const s of requested) {
|
|
6179
|
-
sources[s] = {
|
|
6180
|
-
available: false,
|
|
6181
|
-
reason: `the git source could not answer, so the ${s} source cannot locate any commit`
|
|
6182
|
-
};
|
|
6183
|
-
}
|
|
6184
|
-
} else {
|
|
6185
|
-
const result = await deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested);
|
|
6186
|
-
Object.assign(sources, result.status);
|
|
6187
|
-
reviews = result.records;
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
let commanded = /* @__PURE__ */ new Map();
|
|
6191
|
-
const doomed = Object.values(sources).some((s) => !s.available);
|
|
6192
|
-
if (consulted.has("command") && ctx.commands !== void 0 && !doomed) {
|
|
6193
|
-
const wanted = new Set(ctx.fields);
|
|
6194
|
-
const commands = Object.fromEntries(
|
|
6195
|
-
Object.entries(ctx.commands).filter(([field]) => wanted.has(field))
|
|
6196
|
-
);
|
|
6197
|
-
if (Object.keys(commands).length > 0) {
|
|
6198
|
-
const result = await deriveFromCommands(inputs, commands, { cwd: ctx.configDir ?? ctx.cwd });
|
|
6199
|
-
sources.command = result.status;
|
|
6200
|
-
commanded = result.records;
|
|
6201
|
-
}
|
|
6202
|
-
}
|
|
6203
|
-
const records = /* @__PURE__ */ new Map();
|
|
6204
|
-
for (const input of inputs) {
|
|
6205
|
-
const fields = {};
|
|
6206
|
-
const g = git2.get(input.label);
|
|
6207
|
-
const r = reviews.get(input.label);
|
|
6208
|
-
const c = commanded.get(input.label);
|
|
6209
|
-
for (const field of ctx.fields) {
|
|
6210
|
-
let value = null;
|
|
6211
|
-
for (const source of sourcesFor(field, ctx.commands)) {
|
|
6212
|
-
if (!consulted.has(source)) continue;
|
|
6213
|
-
const candidate = valueFrom(source, field, g, r, owners.get(input.label), c);
|
|
6214
|
-
if (candidate !== null) {
|
|
6215
|
-
value = candidate;
|
|
6216
|
-
break;
|
|
6217
|
-
}
|
|
6218
|
-
}
|
|
6219
|
-
fields[field] = value;
|
|
6220
|
-
}
|
|
6221
|
-
records.set(input.label, { file: input.label, fields });
|
|
6222
|
-
}
|
|
6223
|
-
return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
|
|
6224
|
-
}
|
|
6225
|
-
function valueFrom(source, field, git2, reviews, owner, commanded) {
|
|
6226
|
-
switch (source) {
|
|
6227
|
-
case "git":
|
|
6228
|
-
if (git2 === void 0 || !isBuiltinField(field) || field === "owner") return null;
|
|
6229
|
-
return git2[field];
|
|
6230
|
-
case "command":
|
|
6231
|
-
return commanded?.[field] ?? null;
|
|
6232
|
-
case "codeowners":
|
|
6233
|
-
return field === "owner" ? owner ?? null : null;
|
|
6234
|
-
case "github":
|
|
6235
|
-
case "gitlab": {
|
|
6236
|
-
if (reviews === void 0) return null;
|
|
6237
|
-
if (field !== "reviewed-by" && field !== "last-reviewed") return null;
|
|
6238
|
-
const fact = reviews[field];
|
|
6239
|
-
return fact !== null && fact.source === source ? fact : null;
|
|
6240
|
-
}
|
|
6241
|
-
}
|
|
6242
|
-
}
|
|
6243
|
-
async function deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested) {
|
|
6244
|
-
const byRoot = /* @__PURE__ */ new Map();
|
|
6245
|
-
for (const input of inputs) {
|
|
6246
|
-
const root = rootFor(input);
|
|
6247
|
-
if (root === null) continue;
|
|
6248
|
-
const bucket = byRoot.get(root) ?? [];
|
|
6249
|
-
bucket.push(input);
|
|
6250
|
-
byRoot.set(root, bucket);
|
|
6251
|
-
}
|
|
6252
|
-
const status = {};
|
|
6253
|
-
const records = /* @__PURE__ */ new Map();
|
|
6254
|
-
const unavailable2 = (which, reason) => {
|
|
6255
|
-
for (const s of which) status[s] = { available: false, reason };
|
|
6256
|
-
return { status, records: /* @__PURE__ */ new Map() };
|
|
6257
|
-
};
|
|
6258
|
-
if (byRoot.size === 0) {
|
|
6259
|
-
return unavailable2(requested, "no git repository contains the documents");
|
|
6260
|
-
}
|
|
6261
|
-
const cacheDir = resolve5(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
|
|
6262
|
-
for (const [root, bucket] of byRoot) {
|
|
6263
|
-
const client = ctx.reviews ?? await createReviewClient(root, { cwd: ctx.cwd, cache: ctx.cache, cacheDir, requested });
|
|
6264
|
-
const identity = await client.detect();
|
|
6265
|
-
if (identity === null) {
|
|
6266
|
-
const s = await client.status();
|
|
6267
|
-
return unavailable2(requested, s.reason ?? "no origin remote to tell GitHub from GitLab");
|
|
6268
|
-
}
|
|
6269
|
-
const source = identity.kind;
|
|
6270
|
-
if (!requested.includes(source)) {
|
|
6271
|
-
return unavailable2(
|
|
6272
|
-
requested,
|
|
6273
|
-
`the origin remote is ${identity.host}, which is ${HOST_NAME[source]}; add ${source} to sources, or drop reviewed-by and last-reviewed from the managed fields`
|
|
6274
|
-
);
|
|
6275
|
-
}
|
|
6276
|
-
const result = await deriveFromReviews(
|
|
6277
|
-
bucket.map((input) => ({
|
|
6278
|
-
label: input.label,
|
|
6279
|
-
sha: git2.get(input.label)?.lastBodyCommit ?? null
|
|
6280
|
-
})),
|
|
6281
|
-
client
|
|
6282
|
-
);
|
|
6283
|
-
if (!result.status.available) {
|
|
6284
|
-
status[source] = result.status;
|
|
6285
|
-
return { status, records: /* @__PURE__ */ new Map() };
|
|
6286
|
-
}
|
|
6287
|
-
status[source] = result.status;
|
|
6288
|
-
for (const [label, facts] of result.records) records.set(label, facts);
|
|
6289
|
-
}
|
|
6290
|
-
return { status, records };
|
|
6291
|
-
}
|
|
6292
|
-
|
|
6293
|
-
// src/meta/core/derive/table.ts
|
|
6294
|
-
var DERIVED_VIEW = "derived";
|
|
6295
|
-
var DERIVED_ROWS = "_derived_rows";
|
|
6296
|
-
var RESOLVED_VIEW = "resolved";
|
|
6297
|
-
var ORIGIN_COLUMN = "_origin";
|
|
6298
|
-
var SOURCES_COLUMN = "_sources";
|
|
6299
|
-
function derivedColumns(commands) {
|
|
6300
|
-
return ["_path", ...derivableFields(commands), SOURCES_COLUMN];
|
|
6301
|
-
}
|
|
6302
|
-
function mentionsDerived(sql) {
|
|
6303
|
-
return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
|
|
6304
|
-
}
|
|
6305
|
-
function fieldsForSql(sql, fields) {
|
|
6306
|
-
const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
|
|
6307
|
-
if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
|
|
6308
|
-
return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
|
|
6309
|
-
}
|
|
6310
|
-
return fields.filter(named);
|
|
6311
|
-
}
|
|
6312
|
-
function escapeRegExp(s) {
|
|
6313
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6314
|
-
}
|
|
6315
|
-
function createDerivedView(db, records, columns) {
|
|
6316
|
-
const quoted = columns.map(quoteIdent);
|
|
6317
|
-
db.exec(
|
|
6318
|
-
`CREATE TABLE ${DERIVED_ROWS} (${quoted.map((c, i) => i === 0 ? `${c} TEXT PRIMARY KEY` : `${c} TEXT`).join(", ")})`
|
|
6319
|
-
);
|
|
6320
|
-
const insert = db.prepare(
|
|
6321
|
-
`INSERT INTO ${DERIVED_ROWS} VALUES (${quoted.map(() => "?").join(", ")})`
|
|
6322
|
-
);
|
|
6323
|
-
const fields = columns.slice(1, -1);
|
|
6324
|
-
db.exec("BEGIN");
|
|
6325
|
-
let committed = false;
|
|
6326
|
-
try {
|
|
6327
|
-
for (const [label, record] of records) {
|
|
6328
|
-
const sources = {};
|
|
6329
|
-
const values = fields.map((field) => {
|
|
6330
|
-
const derived = record.fields[field];
|
|
6331
|
-
if (derived == null) return null;
|
|
6332
|
-
sources[field] = { source: derived.source, evidence: derived.evidence };
|
|
6333
|
-
return bindValue(derived.value);
|
|
6334
|
-
});
|
|
6335
|
-
insert.run(label, ...values, JSON.stringify(sources));
|
|
6336
|
-
}
|
|
6337
|
-
db.exec("COMMIT");
|
|
6338
|
-
committed = true;
|
|
6339
|
-
} finally {
|
|
6340
|
-
if (!committed) db.exec("ROLLBACK");
|
|
6341
|
-
}
|
|
6342
|
-
db.exec(`CREATE VIEW ${DERIVED_VIEW} AS SELECT * FROM ${DERIVED_ROWS}`);
|
|
6343
|
-
}
|
|
6344
|
-
function resolvedColumns(docsColumns, commands) {
|
|
6345
|
-
const columns = ["_path"];
|
|
6346
|
-
const seen = /* @__PURE__ */ new Set(["_path", ORIGIN_COLUMN, SOURCES_COLUMN]);
|
|
6347
|
-
for (const column of docsColumns) {
|
|
6348
|
-
if (seen.has(column) || RESERVED.has(column)) continue;
|
|
6349
|
-
seen.add(column);
|
|
6350
|
-
columns.push(column);
|
|
6351
|
-
}
|
|
6352
|
-
for (const field of derivableFields(commands)) {
|
|
6353
|
-
if (seen.has(field)) continue;
|
|
6354
|
-
seen.add(field);
|
|
6355
|
-
columns.push(field);
|
|
6356
|
-
}
|
|
6357
|
-
columns.push(ORIGIN_COLUMN, SOURCES_COLUMN);
|
|
6358
|
-
return columns;
|
|
6359
|
-
}
|
|
6360
|
-
function mentionsResolved(sql) {
|
|
6361
|
-
return /\b(?:from|join|update|into|table|view)\s+(?:"resolved"|resolved\b)/i.test(sql);
|
|
6362
|
-
}
|
|
6363
|
-
function jsonPath(field) {
|
|
6364
|
-
return `$.${JSON.stringify(field)}`;
|
|
6365
|
-
}
|
|
6366
|
-
function sqlString(value) {
|
|
6367
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
6368
|
-
}
|
|
6369
|
-
function createResolvedView(db, docsColumns, commands) {
|
|
6370
|
-
const columns = resolvedColumns(docsColumns, commands);
|
|
6371
|
-
const fields = columns.slice(1, -2);
|
|
6372
|
-
const inDocs = new Set(docsColumns);
|
|
6373
|
-
const inDerived = new Set(derivableFields(commands));
|
|
6374
|
-
const selects = [`d.${quoteIdent("_path")} AS ${quoteIdent("_path")}`];
|
|
6375
|
-
const origin = [];
|
|
6376
|
-
for (const field of fields) {
|
|
6377
|
-
const path = sqlString(jsonPath(field));
|
|
6378
|
-
const asserted = `json_type(d.${quoteIdent("_data")}, ${path}) IS NOT NULL`;
|
|
6379
|
-
const assertedValue = inDocs.has(field) ? `d.${quoteIdent(field)}` : `d.${quoteIdent("_data")} ->> ${path}`;
|
|
6380
|
-
if (inDerived.has(field)) {
|
|
6381
|
-
const derivedValue = `r.${quoteIdent(field)}`;
|
|
6382
|
-
selects.push(
|
|
6383
|
-
`CASE WHEN ${asserted} THEN ${assertedValue} ELSE ${derivedValue} END AS ${quoteIdent(field)}`
|
|
6384
|
-
);
|
|
6385
|
-
origin.push(
|
|
6386
|
-
`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' WHEN ${derivedValue} IS NOT NULL THEN 'derived' END`
|
|
6387
|
-
);
|
|
6388
|
-
} else {
|
|
6389
|
-
selects.push(`${assertedValue} AS ${quoteIdent(field)}`);
|
|
6390
|
-
origin.push(`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' END`);
|
|
6391
|
-
}
|
|
6392
|
-
}
|
|
6393
|
-
selects.push(
|
|
6394
|
-
`json_patch('{}', json_object(${origin.join(", ")})) AS ${quoteIdent(ORIGIN_COLUMN)}`
|
|
6395
|
-
);
|
|
6396
|
-
selects.push(`r.${quoteIdent(SOURCES_COLUMN)} AS ${quoteIdent(SOURCES_COLUMN)}`);
|
|
6397
|
-
db.exec(
|
|
6398
|
-
`CREATE VIEW ${RESOLVED_VIEW} AS SELECT ${selects.join(", ")} FROM docs d LEFT JOIN ${DERIVED_ROWS} r USING (${quoteIdent("_path")})`
|
|
6399
|
-
);
|
|
6400
|
-
}
|
|
6401
|
-
async function deriveForTable(inputs, ctx, hint, fields) {
|
|
6402
|
-
const derive = ctx.config?.derive;
|
|
6403
|
-
const commands = commandsOf(derive);
|
|
6404
|
-
const managesProvenance = derive?.fields?.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
|
|
6405
|
-
const result = await deriveMetadata(inputs, {
|
|
6406
|
-
cwd: ctx.cwd,
|
|
6407
|
-
base: ctx.base,
|
|
6408
|
-
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
6409
|
-
sources: derive?.sources ?? [...DERIVE_SOURCES],
|
|
6410
|
-
fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
|
|
6411
|
-
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
6412
|
-
...commands !== void 0 ? { commands } : {},
|
|
6413
|
-
cache: ctx.cache,
|
|
6414
|
-
now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
|
|
6415
|
-
machines: machinesOf(derive)
|
|
6416
|
-
});
|
|
6417
|
-
assertSourcesAvailable(result.sources, hint);
|
|
6418
|
-
return result.records;
|
|
6419
|
-
}
|
|
6420
|
-
|
|
6421
|
-
// src/meta/core/checks.ts
|
|
6422
|
-
var CHECK_KEYWORD = "check";
|
|
6423
|
-
function checkSchemaRef(name) {
|
|
6424
|
-
if (!CHECK_NAME.test(name) || name.toLowerCase().endsWith(".json")) {
|
|
6425
|
-
throw new DocmetaError(
|
|
6426
|
-
`check name "${name}" must match [a-z0-9][a-z0-9._-]* and not end in ".json" \u2014 the name is part of each finding's identity (check:<name>).`
|
|
6427
|
-
);
|
|
6428
|
-
}
|
|
6429
|
-
return `check:${name}`;
|
|
6430
|
-
}
|
|
6431
|
-
var CONVENTION_COLUMNS = /* @__PURE__ */ new Set(["path", "line", "key", "message"]);
|
|
6432
|
-
function rowsToFindings(name, columns, rows, loaded) {
|
|
6433
|
-
if (!columns.includes("path")) {
|
|
6434
|
-
throw new DocmetaError(
|
|
6435
|
-
`check "${name}": the result has no \`path\` column. A check's SELECT names its findings through the column convention: \`path\` (required), and optionally \`line\`, \`key\`, and \`message\`; any other column is folded into the message.`
|
|
6436
|
-
);
|
|
6437
|
-
}
|
|
6438
|
-
const findings = /* @__PURE__ */ new Map();
|
|
6439
|
-
const extras = columns.filter((c) => !CONVENTION_COLUMNS.has(c));
|
|
6440
|
-
for (const row of rows) {
|
|
6441
|
-
const path = row.path;
|
|
6442
|
-
if (typeof path !== "string" || loaded !== void 0 && !loaded.has(path)) {
|
|
6443
|
-
throw new DocmetaError(
|
|
6444
|
-
`check "${name}": a result row names ${JSON.stringify(path)}, which this run did not load \u2014 a finding must attach to a file of the corpus. Fix the check's \`path\` column.`
|
|
6445
|
-
);
|
|
6446
|
-
}
|
|
6447
|
-
const key = typeof row.key === "string" && row.key !== "" ? row.key : void 0;
|
|
6448
|
-
const line = asLine(row.line);
|
|
6449
|
-
const message = typeof row.message === "string" && row.message !== "" ? row.message : synthesizeMessage(extras, row);
|
|
6450
|
-
const err = {
|
|
6451
|
-
schema: checkSchemaRef(name),
|
|
6452
|
-
keyword: CHECK_KEYWORD,
|
|
6453
|
-
// RFC 6901 escaping, or a key containing `/` or `~` would build a
|
|
6454
|
-
// pointer that parses as a different (nested) location.
|
|
6455
|
-
instancePath: key === void 0 ? "" : `/${escapePointerSegment(key)}`,
|
|
6456
|
-
message,
|
|
6457
|
-
...line === void 0 ? {} : { line }
|
|
6458
|
-
};
|
|
6459
|
-
const bucket = findings.get(path);
|
|
6460
|
-
if (bucket) bucket.push(err);
|
|
6461
|
-
else findings.set(path, [err]);
|
|
6462
|
-
}
|
|
6463
|
-
return findings;
|
|
6464
|
-
}
|
|
6465
|
-
function asLine(value) {
|
|
6466
|
-
const n = typeof value === "bigint" ? Number(value) : typeof value === "number" ? value : void 0;
|
|
6467
|
-
return n !== void 0 && Number.isInteger(n) && n > 0 ? n : void 0;
|
|
6468
|
-
}
|
|
6469
|
-
function synthesizeMessage(extras, row) {
|
|
6470
|
-
const pairs = extras.flatMap((c) => {
|
|
6471
|
-
const value = row[c];
|
|
6472
|
-
if (value === null || value === void 0) return [];
|
|
6473
|
-
const text2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : JSON.stringify(value);
|
|
6474
|
-
return [`${c}=${text2}`];
|
|
6475
|
-
});
|
|
6476
|
-
return pairs.length > 0 ? pairs.join(", ") : "check matched";
|
|
6477
|
-
}
|
|
6478
|
-
async function runChecks(checks, entries, ctx = {}) {
|
|
6479
|
-
const merged = /* @__PURE__ */ new Map();
|
|
6480
|
-
if (checks.length === 0) return merged;
|
|
6481
|
-
const { DatabaseSync } = await loadSqlite();
|
|
6482
|
-
const db = new DatabaseSync(":memory:");
|
|
6483
|
-
try {
|
|
6484
|
-
const dataColumns = corpusDataColumns(entries);
|
|
6485
|
-
createDocsTable(db, entries, dataColumns);
|
|
6486
|
-
registerLineFor(db, entries);
|
|
6487
|
-
createCollectionViews(db, collectCollections(entries, ctx));
|
|
6488
|
-
const wantsDerived = checks.filter(
|
|
6489
|
-
(c) => mentionsDerived(c.query) || mentionsResolved(c.query)
|
|
6490
|
-
);
|
|
6491
|
-
if (wantsDerived.length > 0) {
|
|
6492
|
-
if (ctx.derive === void 0) {
|
|
6493
|
-
throw new DocmetaError(
|
|
6494
|
-
`check "${wantsDerived[0]?.name ?? ""}": the SQL names the derived or resolved table, but this run cannot derive. Run it through \`manni meta validate\`, or drop the reference.`
|
|
6495
|
-
);
|
|
6496
|
-
}
|
|
6497
|
-
const commands = ctx.commands;
|
|
6498
|
-
const readable = derivableFields(commands);
|
|
6499
|
-
const fields = /* @__PURE__ */ new Set();
|
|
6500
|
-
for (const c of wantsDerived) for (const f of fieldsForSql(c.query, readable)) fields.add(f);
|
|
6501
|
-
createDerivedView(db, await ctx.derive([...fields]), derivedColumns(commands));
|
|
6502
|
-
createResolvedView(db, dataColumns, commands);
|
|
6503
|
-
}
|
|
6504
|
-
db.exec("PRAGMA query_only = 1");
|
|
6505
|
-
const loaded = new Set(entries.map((e) => e.label));
|
|
6506
|
-
for (const check of checks) {
|
|
6507
|
-
const head = stripLeadingTrivia(check.query);
|
|
6508
|
-
if (/^(attach|vacuum)\b/i.test(head)) {
|
|
6509
|
-
throw new DocmetaError(
|
|
6510
|
-
`check "${check.name}": ${head.split(/\s/, 1)[0]?.toUpperCase() ?? "that statement"} is refused: it can write outside the docs table.`
|
|
6511
|
-
);
|
|
6512
|
-
}
|
|
6513
|
-
let tokens2;
|
|
6514
|
-
try {
|
|
6515
|
-
tokens2 = collectNamedParameters(check.query);
|
|
6516
|
-
} catch (err) {
|
|
6517
|
-
throw new DocmetaError(
|
|
6518
|
-
`check "${check.name}": ${errorMessage(err)}`
|
|
6519
|
-
);
|
|
6520
|
-
}
|
|
6521
|
-
if (tokens2.length > 0) {
|
|
6522
|
-
throw new DocmetaError(
|
|
6523
|
-
`check "${check.name}": the SQL references ${tokens2.join(", ")}, but checks bind no parameters \u2014 an unbound name binds NULL and matches nothing, so the gate would pass on a typo. Inline the value in the SQL.`
|
|
6524
|
-
);
|
|
6525
|
-
}
|
|
6526
|
-
let columns;
|
|
6527
|
-
let rows;
|
|
6528
|
-
try {
|
|
6529
|
-
assertSingleStatement(check.query);
|
|
6530
|
-
const stmt = db.prepare(check.query);
|
|
6531
|
-
columns = stmt.columns().map((c) => c.name);
|
|
6532
|
-
rows = stmt.all();
|
|
6533
|
-
} catch (err) {
|
|
6534
|
-
const message = errorMessage(err);
|
|
6535
|
-
throw new DocmetaError(
|
|
6536
|
-
message.includes("attempt to write a readonly database") ? `check "${check.name}": ${message} \u2014 checks are read-only by design; a check is a SELECT over the projection. Edit the corpus with \`manni meta query\` instead.` : `check "${check.name}": ${message}`
|
|
6537
|
-
);
|
|
6538
|
-
}
|
|
6539
|
-
for (const [path, errs] of rowsToFindings(
|
|
6540
|
-
check.name,
|
|
6541
|
-
columns,
|
|
6542
|
-
rows,
|
|
6543
|
-
loaded
|
|
6544
|
-
)) {
|
|
6545
|
-
const bucket = merged.get(path);
|
|
6546
|
-
if (bucket) bucket.push(...errs);
|
|
6547
|
-
else merged.set(path, errs);
|
|
6548
|
-
}
|
|
6549
|
-
}
|
|
6550
|
-
} finally {
|
|
6551
|
-
db.close();
|
|
6552
|
-
}
|
|
6553
|
-
return merged;
|
|
6554
|
-
}
|
|
6555
|
-
|
|
6556
|
-
// src/meta/core/derive/provenance-place.ts
|
|
6557
|
-
import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve6, sep as sep6 } from "node:path";
|
|
6558
|
-
var toPosix3 = (p) => p.split(sep6).join("/");
|
|
6559
|
-
function provenanceManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
|
|
6560
|
-
const out = [];
|
|
6561
|
-
for (const collection of collections) {
|
|
6562
|
-
for (const manifest of collection.externalMetadata) {
|
|
6563
|
-
const implied = manifest.keys === void 0;
|
|
6564
|
-
if (!implied && manifest.keys?.includes(field) !== true) continue;
|
|
6565
|
-
const url = classifyRef(manifest.file).kind === "url";
|
|
6566
|
-
if (url && !implied) throw new DocmetaError(urlRefusal(collection.name, field));
|
|
6567
|
-
if (url) {
|
|
6568
|
-
out.push({
|
|
6569
|
-
collection: collection.name,
|
|
6570
|
-
absPath: manifest.file,
|
|
6571
|
-
file: manifest.file,
|
|
6572
|
-
join: externalMetadataJoin(manifest),
|
|
6573
|
-
perPage: false,
|
|
6574
|
-
written: manifest.file,
|
|
6575
|
-
implied: { collection, manifest, url }
|
|
6576
|
-
});
|
|
6577
|
-
continue;
|
|
6578
|
-
}
|
|
6579
|
-
const absPath = isAbsolute5(manifest.file) ? manifest.file : resolve6(configDir, manifest.file);
|
|
6580
|
-
const perPage = hasPagePlaceholder(manifest.file);
|
|
6581
|
-
out.push({
|
|
6582
|
-
collection: collection.name,
|
|
6583
|
-
absPath,
|
|
6584
|
-
file: perPage ? manifest.file : reportedPath(absPath, base),
|
|
6585
|
-
join: externalMetadataJoin(manifest),
|
|
6586
|
-
perPage,
|
|
6587
|
-
written: manifest.file,
|
|
6588
|
-
...implied ? { implied: { collection, manifest, url } } : {}
|
|
6589
|
-
});
|
|
6590
|
-
}
|
|
6591
|
-
}
|
|
6592
|
-
return out;
|
|
6593
|
-
}
|
|
6594
|
-
function urlRefusal(collection, field) {
|
|
6595
|
-
return `collection ${collection}: ${field} cannot come from a URL manifest, because manni meta derive writes it.`;
|
|
6596
|
-
}
|
|
6597
|
-
function provenancePlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
|
|
6598
|
-
if (manifests.length === 0) return void 0;
|
|
6599
|
-
const members = new Set(memberOf(collections, configDir, base, label));
|
|
6600
|
-
const owning = manifests.filter(
|
|
6601
|
-
(m) => members.has(m.collection) && (m.implied === void 0 || ownsKey(m.implied.collection, m.implied.manifest, field, marked))
|
|
6602
|
-
);
|
|
6603
|
-
const [declared, second] = owning;
|
|
6604
|
-
if (declared === void 0) return void 0;
|
|
6605
|
-
if (second !== void 0) {
|
|
6606
|
-
throw new DocmetaError(
|
|
6607
|
-
`${label} is in collections ${declared.collection} and ${second.collection}, and both keep ${field} in a manifest.`
|
|
6608
|
-
);
|
|
6609
|
-
}
|
|
6610
|
-
if (declared.implied?.url === true) throw new DocmetaError(urlRefusal(declared.collection, field));
|
|
6611
|
-
const manifest = declared.perPage ? ownManifest(declared, label, collections, configDir, base) : declared;
|
|
6612
|
-
if (manifest.join === PATH_JOIN) {
|
|
6613
|
-
const entry = toPosix3(relative7(configDir, resolve6(base, label)));
|
|
6614
|
-
return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
|
|
6615
|
-
}
|
|
6616
|
-
const value = data[manifest.join];
|
|
6617
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
6618
|
-
throw new DocmetaError(
|
|
6619
|
-
`${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${field} has no entry there.`
|
|
6620
|
-
);
|
|
6621
|
-
}
|
|
6622
|
-
return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
|
|
6623
|
-
}
|
|
6624
|
-
function ownManifest(manifest, label, collections, configDir, base) {
|
|
6625
|
-
const resolved = pageManifestPath(manifest.written, configDir, resolve6(base, label));
|
|
6626
|
-
if ("outside" in resolved) {
|
|
6627
|
-
const declared = collections.find((c) => c.name === manifest.collection)?.externalMetadata.find((m) => m.file === manifest.written);
|
|
6628
|
-
throw new DocmetaError(
|
|
6629
|
-
outsideRefusal(declared ?? { file: manifest.written }, manifest.collection, resolved.pageRel)
|
|
6630
|
-
);
|
|
6631
|
-
}
|
|
6632
|
-
return { ...manifest, absPath: resolved.abs, file: reportedPath(resolved.abs, base) };
|
|
6633
|
-
}
|
|
6634
|
-
function readerManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
|
|
6635
|
-
const local = collections.map((c) => ({
|
|
6636
|
-
...c,
|
|
6637
|
-
externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
|
|
6638
|
-
}));
|
|
6639
|
-
return provenanceManifests(local, configDir, base, field);
|
|
6640
|
-
}
|
|
6641
|
-
function readerPlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
|
|
6642
|
-
try {
|
|
6643
|
-
const place = provenancePlace(label, data, manifests, collections, configDir, base, marked, field);
|
|
6644
|
-
return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
|
|
6645
|
-
} catch (err) {
|
|
6646
|
-
if (err instanceof DocmetaError) return void 0;
|
|
6647
|
-
throw err;
|
|
6648
|
-
}
|
|
6649
|
-
}
|
|
6650
|
-
function stampManifests(label, data, collections, configDir, base, marked, fields) {
|
|
6651
|
-
const out = {};
|
|
6652
|
-
for (const field of STAMPED_DATE_FIELDS) {
|
|
6653
|
-
if (!fields.includes(field)) continue;
|
|
6654
|
-
const manifests = readerManifests(collections, configDir, base, field);
|
|
6655
|
-
const place = readerPlace(label, data, manifests, collections, configDir, base, marked, field);
|
|
6656
|
-
if (place !== void 0) out[field] = place;
|
|
6657
|
-
}
|
|
6658
|
-
return out;
|
|
6659
|
-
}
|
|
6660
|
-
|
|
6661
|
-
// src/shared/cli-options.ts
|
|
6662
|
-
import { basename as basename4, relative as relative8 } from "node:path";
|
|
6663
|
-
function collect(value, prev) {
|
|
6664
|
-
return prev.concat([value]);
|
|
6665
|
-
}
|
|
6666
|
-
function splitList(value) {
|
|
6667
|
-
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6668
|
-
}
|
|
6669
|
-
var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
|
|
6670
|
-
async function readStdin() {
|
|
6671
|
-
const chunks = [];
|
|
6672
|
-
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6673
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
6674
|
-
}
|
|
6675
|
-
function configOption(value) {
|
|
6676
|
-
if (value === false) return { noConfig: true };
|
|
6677
|
-
return typeof value === "string" ? { configPath: value } : {};
|
|
6678
|
-
}
|
|
6679
|
-
function explicitFalse(value) {
|
|
6680
|
-
return value === false ? false : void 0;
|
|
6681
|
-
}
|
|
6682
|
-
function reportConfig(toStdout, cwd) {
|
|
6683
|
-
return (info) => {
|
|
6684
|
-
const where = (relative8(cwd, info.dir) || ".").replace(/\\/g, "/");
|
|
6685
|
-
const line = `Using ${basename4(info.path)} (${where})
|
|
6686
|
-
`;
|
|
6687
|
-
(toStdout ? process.stdout : process.stderr).write(line);
|
|
6688
|
-
};
|
|
6689
|
-
}
|
|
6690
|
-
|
|
6691
|
-
// src/meta/commands/validate.ts
|
|
6692
|
-
function parseErrorResult(file, format, message, keyword) {
|
|
6693
|
-
const err = {
|
|
6694
|
-
schema: "(parse)",
|
|
6695
|
-
instancePath: "",
|
|
6696
|
-
message,
|
|
6697
|
-
keyword
|
|
6698
|
-
};
|
|
6699
|
-
return { file, format, ok: false, schemas: [], errors: [err] };
|
|
6700
|
-
}
|
|
6701
|
-
function once(fn) {
|
|
6702
|
-
let pending;
|
|
6703
|
-
return () => {
|
|
6704
|
-
pending ??= fn();
|
|
6705
|
-
return pending;
|
|
6706
|
-
};
|
|
6707
|
-
}
|
|
6708
|
-
async function runValidate(opts) {
|
|
6709
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
6710
|
-
const run2 = await resolveRunConfig({
|
|
6711
|
-
cwd,
|
|
6712
|
-
configPath: opts.configPath,
|
|
6713
|
-
noConfig: opts.noConfig,
|
|
6714
|
-
inputs: opts.inputs,
|
|
6715
|
-
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
6716
|
-
onConfigLoaded: opts.onConfigLoaded
|
|
6717
|
-
});
|
|
6718
|
-
const {
|
|
6719
|
-
config,
|
|
6720
|
-
inputs,
|
|
6721
|
-
base,
|
|
6722
|
-
configDir,
|
|
6723
|
-
collections,
|
|
6724
|
-
declaredCollections,
|
|
6725
|
-
fromCollections,
|
|
6726
|
-
configFile
|
|
6727
|
-
} = run2;
|
|
6728
|
-
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
6729
|
-
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
6730
|
-
if (inputs.length === 0) {
|
|
6731
|
-
throw new DocmetaError(
|
|
6732
|
-
"No files to validate. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
6733
|
-
);
|
|
6734
|
-
}
|
|
6735
|
-
const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
|
|
6736
|
-
if (opts.as && !forcedExtractor) {
|
|
6737
|
-
throw new DocmetaError(
|
|
6738
|
-
`Unknown format "${opts.as}". Supported extensions: ${supportedExtensions().join(", ")}.`
|
|
6739
|
-
);
|
|
6740
|
-
}
|
|
6741
|
-
const exts = opts.exts ?? (forcedExtractor ? forcedExtractor.extensions : void 0);
|
|
6742
|
-
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
6743
|
-
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
6744
|
-
const exclude = opts.exclude ?? [];
|
|
6745
|
-
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
6746
|
-
inputs: fileInputs,
|
|
6747
|
-
exts,
|
|
6748
|
-
exclude,
|
|
6749
|
-
cwd: base,
|
|
6750
|
-
allowEmpty,
|
|
6751
|
-
...gitignoreOptions({
|
|
6752
|
-
flag: opts.respectGitignore,
|
|
6753
|
-
configured: config?.respectGitignore,
|
|
6754
|
-
onNotice: opts.onNotice
|
|
6755
|
-
})
|
|
6756
|
-
});
|
|
6757
|
-
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
6758
|
-
assertNonEmpty({
|
|
6759
|
-
files,
|
|
6760
|
-
inputs: fileInputs,
|
|
6761
|
-
usingStdin,
|
|
6762
|
-
allowEmpty,
|
|
6763
|
-
exclude,
|
|
6764
|
-
exts,
|
|
6765
|
-
gitignoreSkipped,
|
|
6766
|
-
action: "validated"
|
|
6767
|
-
});
|
|
6768
|
-
const trustRoot = schemaTrustRoot(cwd, configDir);
|
|
6769
|
-
const externalMetadata = await loadExternalMetadata(collections, {
|
|
6770
|
-
configDir: configDir ?? cwd,
|
|
6771
|
-
base,
|
|
6772
|
-
offline: opts.offline ?? config?.offline ?? false,
|
|
6773
|
-
// A `{page}` manifest (0058) is read for exactly the pages this run reads.
|
|
6774
|
-
pages: files.map((file) => resolve7(base, file))
|
|
6775
|
-
});
|
|
6776
|
-
const validator = new Validator(
|
|
6777
|
-
schemaLoadOptions({
|
|
6778
|
-
// The config's directory when a config governs the run, so one project
|
|
6779
|
-
// keeps one cache no matter which directory the command was run from.
|
|
6780
|
-
root: configDir ?? cwd,
|
|
6781
|
-
// A relative file ref belongs to the run's directory, not the cache root.
|
|
6782
|
-
fileBase: cwd,
|
|
6783
|
-
ttlHours: config?.schemaCache?.ttlHours,
|
|
6784
|
-
offline: opts.offline ?? config?.offline,
|
|
6785
|
-
// Built from the rebased config, so a pinned local ref is keyed by the
|
|
6786
|
-
// same absolute spelling `resolveSchemaSet` will hand to `loadSchema`.
|
|
6787
|
-
pins: collectSchemaPins(config),
|
|
6788
|
-
registered: config?.registered
|
|
6789
|
-
})
|
|
6790
|
-
);
|
|
6791
|
-
const marks = pageMarks({
|
|
6792
|
-
validator,
|
|
6793
|
-
config,
|
|
6794
|
-
cwd,
|
|
6795
|
-
trustRoot,
|
|
6796
|
-
...opts.cliSchemas !== void 0 ? { cliSchemas: opts.cliSchemas } : {}
|
|
6797
|
-
});
|
|
6798
|
-
let relocation;
|
|
6799
|
-
const relocationCtx = () => relocation ??= relocationContext(run2, {
|
|
6800
|
-
cwd,
|
|
6801
|
-
targets: fromCollections ? [] : opts.inputs,
|
|
6802
|
-
validator,
|
|
6803
|
-
...opts.noConfig !== void 0 ? { noConfig: opts.noConfig } : {},
|
|
6804
|
-
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
6805
|
-
});
|
|
6806
|
-
const refFrame = { cwd, base: configDir ?? cwd, runBase: base };
|
|
6807
|
-
const locationFindings = async (label, own, data, refs, members, merged, lineFor) => {
|
|
6808
|
-
const preferences = await validator.locationPreferences(data, refs);
|
|
6809
|
-
if (preferences.size === 0) return [];
|
|
6810
|
-
const owned = new Set(merged.collisions.map((c) => c.key));
|
|
6811
|
-
const declaredMembers = declaredCollections === collections ? members : memberOf(declaredCollections, configDir ?? cwd, base, label);
|
|
6812
|
-
const joins = new Set(
|
|
6813
|
-
declaredCollections.filter((c) => declaredMembers.includes(c.name)).flatMap((c) => c.externalMetadata.map(externalMetadataJoin)).filter((join5) => join5 !== PATH_JOIN)
|
|
6814
|
-
);
|
|
6815
|
-
const findings = [];
|
|
6816
|
-
for (const key of Object.keys(data)) {
|
|
6817
|
-
const preference = preferences.get(key);
|
|
6818
|
-
if (preference === void 0) continue;
|
|
6819
|
-
const ref = canonicalSchemaRef(preference.schema, refFrame);
|
|
6820
|
-
const pointer = externalMetadataPointer(key);
|
|
6821
|
-
const common = {
|
|
6822
|
-
keyword: LOCATION_FINDING_KEYWORD,
|
|
6823
|
-
subject: key,
|
|
6824
|
-
instancePath: pointer,
|
|
6825
|
-
severity: "warning"
|
|
6826
|
-
};
|
|
6827
|
-
if (Object.hasOwn(own, key)) {
|
|
6828
|
-
if (preference.location !== "external" || owned.has(key) || joins.has(key)) continue;
|
|
6829
|
-
const line = lineFor(key);
|
|
6830
|
-
const none = await homeless(label, own, key);
|
|
6831
|
-
findings.push({
|
|
6832
|
-
schema: LOCATION_EXTERNAL_SCHEMA,
|
|
6833
|
-
...common,
|
|
6834
|
-
// relocate refuses --no-config (U2), so it is no answer there.
|
|
6835
|
-
// Nor is it one for stdin, which relocate refuses: no advice there.
|
|
6836
|
-
message: opts.noConfig ? `"${key}" is stored in the page; ${ref} prefers external metadata, and --no-config leaves it no manifest.` : label === STDIN_LABEL ? `"${key}" is stored in the page; ${ref} prefers external metadata.` : none > 0 ? `"${key}" is stored in the page; ${ref} prefers external metadata, and this document is in none of the ${String(none)} collections.` : `"${key}" is stored in the page; ${ref} prefers external metadata. Run manni meta relocate.`,
|
|
6837
|
-
...line != null ? { line } : {}
|
|
6838
|
-
});
|
|
6839
|
-
continue;
|
|
6840
|
-
}
|
|
6841
|
-
if (preference.location !== "page") continue;
|
|
6842
|
-
const at2 = merged.locate(pointer);
|
|
6843
|
-
if (at2 === void 0) continue;
|
|
6844
|
-
findings.push({
|
|
6845
|
-
schema: LOCATION_PAGE_SCHEMA,
|
|
6846
|
-
...common,
|
|
6847
|
-
// A fetched manifest cannot give a value up, so relocate is no
|
|
6848
|
-
// answer; the value moves in the repository that serves it.
|
|
6849
|
-
message: /^https?:\/\//.test(at2.file) ? `"${key}" is stored in manifest ${at2.file}; ${ref} prefers the page, and a fetched manifest cannot be written. Move it in that repository.` : `"${key}" is stored in manifest ${at2.file}; ${ref} prefers the page. Run manni meta relocate.`,
|
|
6850
|
-
file: at2.file,
|
|
6851
|
-
...at2.line != null ? { line: at2.line } : {}
|
|
6852
|
-
});
|
|
6853
|
-
}
|
|
6854
|
-
return findings;
|
|
6855
|
-
};
|
|
6856
|
-
const homeless = async (label, own, key) => {
|
|
6857
|
-
if (label === STDIN_LABEL) return 0;
|
|
6858
|
-
const home = await keyHome(relocationCtx(), label, own, key);
|
|
6859
|
-
return home.kind === "unowned" && home.home.kind === "none" && home.home.reason === "collections" && home.home.collections > 1 ? home.home.collections : 0;
|
|
6860
|
-
};
|
|
6861
|
-
const results = [];
|
|
6862
|
-
const skippedDefaultOnly = /* @__PURE__ */ new Set();
|
|
6863
|
-
const defaultOnly = (label, members) => {
|
|
6864
|
-
try {
|
|
6865
|
-
return resolveSchemaSetWithSource({
|
|
6866
|
-
filePath: label,
|
|
6867
|
-
fileSchema: void 0,
|
|
6868
|
-
cliSchemas: opts.cliSchemas,
|
|
6869
|
-
config,
|
|
6870
|
-
memberOf: members,
|
|
6871
|
-
fileBase: cwd,
|
|
6872
|
-
trustRoot
|
|
6873
|
-
}).source === "default";
|
|
6874
|
-
} catch {
|
|
6875
|
-
return false;
|
|
6876
|
-
}
|
|
6877
|
-
};
|
|
6878
|
-
const joinHits = /* @__PURE__ */ new Map();
|
|
6879
|
-
const scopedToCollections = (opts.collections?.length ?? 0) > 0;
|
|
6880
|
-
const scopedByFlags = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
|
|
6881
|
-
usingStdin || opts.as !== void 0 || opts.exts !== void 0 || opts.exclude !== void 0 && opts.exclude.length > 0 || opts.respectGitignore !== void 0 || (opts.cliSchemas?.length ?? 0) > 0;
|
|
6882
|
-
const scoped = scopedByFlags || scopedToCollections;
|
|
6883
|
-
const configuredChecks = config?.checks ?? [];
|
|
6884
|
-
if (!scopedByFlags) {
|
|
6885
|
-
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
6886
|
-
const orphans = orphanEntries(externalMetadata, files, base, covered);
|
|
6887
|
-
if (orphans.length > 0) throw orphanError(orphans);
|
|
6888
|
-
}
|
|
6889
|
-
const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
|
|
6890
|
-
const checkEntries = [];
|
|
6891
|
-
const encryptionKey = lazyKey(configFile, opts.env);
|
|
6892
|
-
let unverified = 0;
|
|
6893
|
-
const deriveConfig = config?.derive;
|
|
6894
|
-
const configuredFields = managedFields(deriveConfig);
|
|
6895
|
-
const defaultedFields = deriveConfig !== void 0 && deriveConfig.fields === void 0;
|
|
6896
|
-
const deriveWillRun = deriveConfig !== void 0 && configuredFields.length > 0 && opts.derive !== false;
|
|
6897
|
-
const pageManaged = /* @__PURE__ */ new Map();
|
|
6898
|
-
const derivedChecks = checksWillRun ? configuredChecks.filter((c) => mentionsDerived(c.query) || mentionsResolved(c.query)) : [];
|
|
6899
|
-
const checksNeedDerived = derivedChecks.length > 0;
|
|
6900
|
-
const deriveInputs = [];
|
|
6901
|
-
const managedViews = /* @__PURE__ */ new Map();
|
|
6902
|
-
const keepDeriveInputs = deriveWillRun || checksNeedDerived;
|
|
6903
|
-
const deriveCommands = commandsOf(deriveConfig);
|
|
6904
|
-
const deriveFields = new Set(deriveWillRun && !defaultedFields ? configuredFields : []);
|
|
6905
|
-
const readable = derivableFields(deriveCommands);
|
|
6906
|
-
for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
|
|
6907
|
-
const provenanceManifests2 = keepDeriveInputs && (deriveFields.has(PROVENANCE_FIELD) || deriveWillRun && configuredFields.includes(PROVENANCE_FIELD)) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
|
|
6908
|
-
const derivedOnce = once(async () => {
|
|
6909
|
-
const derived = await deriveMetadata(deriveInputs, {
|
|
6910
|
-
cwd,
|
|
6911
|
-
base,
|
|
6912
|
-
...configDir !== void 0 ? { configDir } : {},
|
|
6913
|
-
sources: deriveConfig?.sources ?? [...DERIVE_SOURCES],
|
|
6914
|
-
fields: [...deriveFields],
|
|
6915
|
-
...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
|
|
6916
|
-
...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
|
|
6917
|
-
cache: opts.cache ?? true,
|
|
6918
|
-
now: opts.now ?? (() => /* @__PURE__ */ new Date()),
|
|
6919
|
-
machines: machinesOf(deriveConfig)
|
|
6920
|
-
});
|
|
6921
|
-
assertSourcesAvailable(
|
|
6922
|
-
derived.sources,
|
|
6923
|
-
deriveWillRun ? "pass --no-derive to skip the comparison, or narrow derive.sources" : "narrow derive.sources in manni.config.yaml"
|
|
6924
|
-
);
|
|
6925
|
-
for (const [name, status] of Object.entries(derived.sources)) {
|
|
6926
|
-
if (status.available && status.reason !== void 0) {
|
|
6927
|
-
opts.onNotice?.(`${name}: ${status.reason}`);
|
|
6928
|
-
}
|
|
6929
|
-
}
|
|
6930
|
-
return derived;
|
|
6931
|
-
});
|
|
6932
|
-
const processOne = async (label, content, extension) => {
|
|
6933
|
-
const extractor = forcedExtractor ?? extractorForExtension(extension);
|
|
6934
|
-
if (!extractor) {
|
|
6935
|
-
throw new DocmetaError(
|
|
6936
|
-
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
6937
|
-
);
|
|
6938
|
-
}
|
|
6939
|
-
const members = membersFor(label);
|
|
6940
|
-
let extracted;
|
|
6941
|
-
try {
|
|
6942
|
-
extracted = extractor.extract(content, label, {
|
|
6943
|
-
elements: resolveElements(label, config, members)
|
|
6944
|
-
});
|
|
6945
|
-
} catch (err) {
|
|
6946
|
-
if (err instanceof DocmetaError) throw err;
|
|
6947
|
-
if (opts.skipDefaultOnly === true && defaultOnly(label, members)) {
|
|
6948
|
-
skippedDefaultOnly.add(label);
|
|
6949
|
-
return;
|
|
6950
|
-
}
|
|
6951
|
-
results.push(
|
|
6952
|
-
parseErrorResult(label, extractor.name, errorMessage(err), "parse")
|
|
6953
|
-
);
|
|
6954
|
-
return;
|
|
6955
|
-
}
|
|
6956
|
-
const merged = await mergeWithMarks(label, extracted, externalMetadata, members, base, {
|
|
6957
|
-
encryptionKey,
|
|
6958
|
-
marks
|
|
6959
|
-
});
|
|
6960
|
-
if (keepDeriveInputs && label !== STDIN_LABEL && (checksNeedDerived || deriveCovers(deriveConfig, members))) {
|
|
6961
|
-
const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base, merged.marked);
|
|
6962
|
-
const owned = declaredCollections.length === 0 ? {} : stampManifests(label, extracted.data, declaredCollections, configDir ?? cwd, base, merged.marked, STAMPED_DATE_FIELDS);
|
|
6963
|
-
deriveInputs.push({
|
|
6964
|
-
label,
|
|
6965
|
-
absPath: resolve7(base, label),
|
|
6966
|
-
content,
|
|
6967
|
-
extracted,
|
|
6968
|
-
...place !== void 0 ? { provenanceManifest: place } : {},
|
|
6969
|
-
...Object.keys(owned).length > 0 ? { stampManifests: owned } : {}
|
|
6970
|
-
});
|
|
6971
|
-
}
|
|
6972
|
-
const own = extracted.data;
|
|
6973
|
-
extracted = merged.extracted;
|
|
6974
|
-
if (deriveWillRun && label !== STDIN_LABEL) {
|
|
6975
|
-
managedViews.set(label, {
|
|
6976
|
-
data: extracted.data,
|
|
6977
|
-
marked: [],
|
|
6978
|
-
hidden: [],
|
|
6979
|
-
refs: [],
|
|
6980
|
-
locate: merged.locate
|
|
6981
|
-
});
|
|
6982
|
-
}
|
|
6983
|
-
for (const j of merged.joins) {
|
|
6984
|
-
const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
|
|
6985
|
-
const line = extracted.lineFor(j.field);
|
|
6986
|
-
const hits = byValue.get(j.value) ?? [];
|
|
6987
|
-
hits.push({
|
|
6988
|
-
label,
|
|
6989
|
-
file: j.file,
|
|
6990
|
-
shown: j.pageValue ?? j.value,
|
|
6991
|
-
...line != null ? { line } : {}
|
|
6992
|
-
});
|
|
6993
|
-
byValue.set(j.value, hits);
|
|
6994
|
-
joinHits.set(j.field, byValue);
|
|
6995
|
-
}
|
|
6996
|
-
if (checksWillRun) checkEntries.push({ label, extracted });
|
|
6997
|
-
let resolved;
|
|
6998
|
-
try {
|
|
6999
|
-
resolved = resolveSchemaSetWithSource({
|
|
7000
|
-
filePath: label,
|
|
7001
|
-
fileSchema: extracted.data[FILE_SCHEMA_KEY],
|
|
7002
|
-
cliSchemas: opts.cliSchemas,
|
|
7003
|
-
config,
|
|
7004
|
-
memberOf: members,
|
|
7005
|
-
// A document's own `$schema` is measured from the run's directory, the
|
|
7006
|
-
// same base `loadSchema` will read it from, and contained to the
|
|
7007
|
-
// repository the run is standing in.
|
|
7008
|
-
fileBase: cwd,
|
|
7009
|
-
trustRoot,
|
|
7010
|
-
onNotice: opts.onNotice
|
|
7011
|
-
});
|
|
7012
|
-
} catch (err) {
|
|
7013
|
-
results.push(
|
|
7014
|
-
parseErrorResult(label, extractor.name, errorMessage(err), "schema")
|
|
7015
|
-
);
|
|
7016
|
-
return;
|
|
7017
|
-
}
|
|
7018
|
-
if (opts.skipDefaultOnly === true && resolved.source === "default") {
|
|
7019
|
-
skippedDefaultOnly.add(label);
|
|
7020
|
-
return;
|
|
7021
|
-
}
|
|
7022
|
-
const schemaSet = resolved.schemas;
|
|
7023
|
-
if (deriveWillRun && defaultedFields && label !== STDIN_LABEL && deriveCovers(deriveConfig, members)) {
|
|
7024
|
-
let claimed = /* @__PURE__ */ new Set();
|
|
7025
|
-
try {
|
|
7026
|
-
claimed = await validator.claimedProperties(schemaSet);
|
|
7027
|
-
} catch (err) {
|
|
7028
|
-
if (!(err instanceof DocmetaError)) throw err;
|
|
7029
|
-
}
|
|
7030
|
-
const own2 = configuredFields.filter((f) => claimed.has(f));
|
|
7031
|
-
pageManaged.set(label, own2);
|
|
7032
|
-
for (const f of own2) deriveFields.add(f);
|
|
7033
|
-
}
|
|
7034
|
-
let errors;
|
|
7035
|
-
try {
|
|
7036
|
-
const view = await encryptionView({
|
|
7037
|
-
data: extracted.data,
|
|
7038
|
-
refs: schemaSet,
|
|
7039
|
-
validator,
|
|
7040
|
-
key: encryptionKey,
|
|
7041
|
-
locate: merged.locate
|
|
7042
|
-
});
|
|
7043
|
-
unverified += view.unverified.length;
|
|
7044
|
-
if (deriveWillRun && label !== STDIN_LABEL) {
|
|
7045
|
-
managedViews.set(label, {
|
|
7046
|
-
data: view.data,
|
|
7047
|
-
marked: view.marked,
|
|
7048
|
-
hidden: [...view.unreadable, ...view.unverified],
|
|
7049
|
-
refs: schemaSet,
|
|
7050
|
-
locate: merged.locate
|
|
7051
|
-
});
|
|
7052
|
-
}
|
|
7053
|
-
errors = [
|
|
7054
|
-
...settleFindings(
|
|
7055
|
-
await validator.validate(
|
|
7056
|
-
view.data,
|
|
7057
|
-
schemaSet,
|
|
7058
|
-
extracted.lineFor,
|
|
7059
|
-
extracted.colFor,
|
|
7060
|
-
merged.locate
|
|
7061
|
-
),
|
|
7062
|
-
view,
|
|
7063
|
-
extracted,
|
|
7064
|
-
merged.locate
|
|
7065
|
-
),
|
|
7066
|
-
...encryptionFindings(view, extracted, merged.locate),
|
|
7067
|
-
// Over the decrypted view, the object validation evaluated: a mark
|
|
7068
|
-
// counts where Ajv takes a branch, and a branch may read a value.
|
|
7069
|
-
...await locationFindings(label, own, view.data, schemaSet, members, merged, extracted.lineFor)
|
|
7070
|
-
];
|
|
7071
|
-
} catch (err) {
|
|
7072
|
-
if (!(err instanceof DocmetaError) || resolved.source !== "document") {
|
|
7073
|
-
throw err;
|
|
7074
|
-
}
|
|
7075
|
-
results.push(
|
|
7076
|
-
parseErrorResult(label, extractor.name, err.message, "schema")
|
|
7077
|
-
);
|
|
7078
|
-
return;
|
|
7079
|
-
}
|
|
7080
|
-
for (const { key, file, collection } of merged.collisions) {
|
|
7081
|
-
const line = extracted.lineFor(key);
|
|
7082
|
-
errors.push({
|
|
7083
|
-
schema: EXTERNAL_OWNED_SCHEMA,
|
|
7084
|
-
keyword: EXTERNAL_KEYWORD,
|
|
7085
|
-
subject: key,
|
|
7086
|
-
instancePath: externalMetadataPointer(key),
|
|
7087
|
-
message: `"${key}" is owned by manifest ${file} (collection ${collection}); remove it from the document`,
|
|
7088
|
-
...line != null ? { line } : {}
|
|
7089
|
-
});
|
|
7090
|
-
}
|
|
7091
|
-
results.push({
|
|
7092
|
-
file: label,
|
|
7093
|
-
format: extractor.name,
|
|
7094
|
-
ok: !errors.some(isErrorSeverity),
|
|
7095
|
-
schemas: schemaSet,
|
|
7096
|
-
errors
|
|
7097
|
-
});
|
|
7098
|
-
};
|
|
7099
|
-
if (usingStdin) {
|
|
7100
|
-
const content = opts.stdinContent ?? "";
|
|
7101
|
-
if (!forcedExtractor) {
|
|
7102
|
-
throw new DocmetaError(
|
|
7103
|
-
STDIN_REQUIRES_AS
|
|
7104
|
-
);
|
|
7105
|
-
}
|
|
7106
|
-
await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
|
|
7107
|
-
}
|
|
7108
|
-
for (const file of files) {
|
|
7109
|
-
const content = await readFile6(resolve7(base, file), "utf8");
|
|
7110
|
-
await processOne(file, content, extname3(file));
|
|
7111
|
-
}
|
|
7112
|
-
if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
|
|
7113
|
-
if (externalMetadata) {
|
|
7114
|
-
const byLabel = new Map(results.map((r) => [r.file, r]));
|
|
7115
|
-
const matched = /* @__PURE__ */ new Map();
|
|
7116
|
-
for (const [field, byValue] of joinHits) {
|
|
7117
|
-
matched.set(field, new Set(byValue.keys()));
|
|
7118
|
-
for (const hits of byValue.values()) {
|
|
7119
|
-
if (hits.length < 2) continue;
|
|
7120
|
-
for (const hit of hits) {
|
|
7121
|
-
const others = hits.filter((h) => h.label !== hit.label).map((h) => h.label).join(", ");
|
|
7122
|
-
const result = byLabel.get(hit.label);
|
|
7123
|
-
if (!result) continue;
|
|
7124
|
-
result.errors.push({
|
|
7125
|
-
schema: EXTERNAL_DUPLICATE_SCHEMA,
|
|
7126
|
-
keyword: EXTERNAL_KEYWORD,
|
|
7127
|
-
subject: hit.shown,
|
|
7128
|
-
instancePath: externalMetadataPointer(field),
|
|
7129
|
-
message: `${hits.length} documents carry ${field} "${hit.shown}"; ${hit.file} cannot tell them apart (${others})`,
|
|
7130
|
-
...hit.line != null ? { line: hit.line } : {}
|
|
7131
|
-
});
|
|
7132
|
-
result.ok = false;
|
|
7133
|
-
}
|
|
7134
|
-
}
|
|
7135
|
-
}
|
|
7136
|
-
if (!scopedByFlags) {
|
|
7137
|
-
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
7138
|
-
const orphans = orphanJoins(externalMetadata, matched, covered);
|
|
7139
|
-
if (orphans.length > 0) throw orphanError(orphans);
|
|
7140
|
-
}
|
|
7141
|
-
}
|
|
7142
|
-
if (configuredChecks.length > 0 && opts.checks !== false) {
|
|
7143
|
-
if (scoped) {
|
|
7144
|
-
opts.onNotice?.(
|
|
7145
|
-
scopedToCollections ? `corpus checks skipped: run is scoped to collections ${collections.map((c) => c.name).join(", ")}` : "corpus checks skipped: run is scoped"
|
|
7146
|
-
);
|
|
7147
|
-
} else {
|
|
7148
|
-
const findings = await runChecks(configuredChecks, checkEntries, {
|
|
7149
|
-
// Every declared collection is a view (0041 rule 7), holding the
|
|
7150
|
-
// members this run loaded — decided by the same path arithmetic
|
|
7151
|
-
// `membersFor` uses, so a check and a `manni meta query` over the same
|
|
7152
|
-
// corpus see the same collections.
|
|
7153
|
-
collections: declaredCollections,
|
|
7154
|
-
...configDir !== void 0 ? { configDir } : {},
|
|
7155
|
-
base,
|
|
7156
|
-
// A check that names the `derived` table (0040) gets the same view
|
|
7157
|
-
// `query` builds, from the run's one derivation — which already
|
|
7158
|
-
// covers every field the checks can read, so the list handed back
|
|
7159
|
-
// here is not needed. Built only when asked for, because building
|
|
7160
|
-
// it spawns git.
|
|
7161
|
-
derive: async () => (await derivedOnce()).records,
|
|
7162
|
-
// Its columns include the configured commands' keys (0042), which a
|
|
7163
|
-
// check's context cannot read from a config since 0041.
|
|
7164
|
-
...deriveCommands !== void 0 ? { commands: deriveCommands } : {}
|
|
7165
|
-
});
|
|
7166
|
-
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7167
|
-
for (const [file, errs] of findings) {
|
|
7168
|
-
const result = byFile.get(file);
|
|
7169
|
-
if (!result && skippedDefaultOnly.has(file)) continue;
|
|
7170
|
-
if (!result) {
|
|
7171
|
-
throw new DocmetaError(
|
|
7172
|
-
`check findings for "${file}" have no validation result to attach to.`
|
|
7173
|
-
);
|
|
7174
|
-
}
|
|
7175
|
-
result.errors.push(...errs);
|
|
7176
|
-
result.ok = result.ok && !errs.some(isErrorSeverity);
|
|
7177
|
-
}
|
|
7178
|
-
}
|
|
7179
|
-
}
|
|
7180
|
-
if (deriveWillRun && deriveInputs.length > 0) {
|
|
7181
|
-
const derived = await derivedOnce();
|
|
7182
|
-
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7183
|
-
for (const input of deriveInputs) {
|
|
7184
|
-
const record = derived.records.get(input.label);
|
|
7185
|
-
const result = byFile.get(input.label);
|
|
7186
|
-
if (!record || !result) continue;
|
|
7187
|
-
const managed = managedViews.get(input.label);
|
|
7188
|
-
const managedHere = !deriveCovers(deriveConfig, membersFor(input.label)) ? [] : defaultedFields ? pageManaged.get(input.label) ?? [] : configuredFields;
|
|
7189
|
-
const fields = managedHere.filter(
|
|
7190
|
-
(field) => !(managed?.hidden.some((p) => isAtOrUnder(p, pointerOf(field))) ?? false)
|
|
7191
|
-
);
|
|
7192
|
-
let marked = [];
|
|
7193
|
-
if (managed !== void 0) {
|
|
7194
|
-
const stamped = { ...managed.data };
|
|
7195
|
-
for (const field of fields) {
|
|
7196
|
-
const d = record.fields[field];
|
|
7197
|
-
if (d != null) stamped[field] = d.value;
|
|
7198
|
-
}
|
|
7199
|
-
marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
|
|
7200
|
-
}
|
|
7201
|
-
const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
|
|
7202
|
-
const findings = fields.flatMap((field) => {
|
|
7203
|
-
if (field === PROVENANCE_FIELD) {
|
|
7204
|
-
const derivation = derived.provenance?.get(input.label);
|
|
7205
|
-
if (derivation === void 0) return [];
|
|
7206
|
-
return provenanceFindings(
|
|
7207
|
-
compareProvenance(provenanceEntries(asserted(field)), derivation),
|
|
7208
|
-
derivation.page.bodyLine
|
|
7209
|
-
);
|
|
7210
|
-
}
|
|
7211
|
-
const compared = compareDerived(field, asserted(field), record.fields[field]);
|
|
7212
|
-
const at2 = pointerOf(field);
|
|
7213
|
-
return staleFindings(
|
|
7214
|
-
[marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
|
|
7215
|
-
input.extracted.lineFor,
|
|
7216
|
-
managed?.locate
|
|
7217
|
-
);
|
|
7218
|
-
});
|
|
7219
|
-
if (findings.length === 0) continue;
|
|
7220
|
-
result.errors.push(...findings);
|
|
7221
|
-
result.ok = false;
|
|
7222
|
-
}
|
|
7223
|
-
}
|
|
7224
|
-
const frame = { cwd, base: configDir ?? cwd, runBase: base };
|
|
7225
|
-
const { results: reported3, baseline } = await settleBaseline(
|
|
7226
|
-
results,
|
|
7227
|
-
resolveBaselineRequest(opts, config?.baseline, configDir, cwd, {
|
|
7228
|
-
current: DEFAULT_BASELINE_PATH,
|
|
7229
|
-
legacy: LEGACY_BASELINE_PATH
|
|
7230
|
-
}),
|
|
7231
|
-
frame
|
|
7232
|
-
);
|
|
7233
|
-
const failed = reported3.filter((r) => !r.ok).length;
|
|
7234
|
-
const count2 = (keep) => reported3.reduce((n, r) => n + r.errors.filter(keep).length, 0);
|
|
7235
|
-
const warnings = count2((e) => e.severity === "warning");
|
|
7236
|
-
const notices = count2((e) => e.severity === "notice");
|
|
7237
|
-
const summary = {
|
|
7238
|
-
files: reported3.length,
|
|
7239
|
-
passed: reported3.length - failed,
|
|
7240
|
-
failed,
|
|
7241
|
-
errors: count2(isErrorSeverity),
|
|
7242
|
-
// Omitted at zero, like `gitignoreSkipped` below: only the location
|
|
7243
|
-
// findings (proposal 0047) are warnings, so a run with no
|
|
7244
|
-
// `x-manni-location` marks keeps the summary as it was.
|
|
7245
|
-
...warnings > 0 ? { warnings } : {},
|
|
7246
|
-
...notices > 0 ? { notices } : {},
|
|
7247
|
-
// Omitted when nothing was skipped: there is nothing to audit, and the
|
|
7248
|
-
// JSON summary stays as it was for every run in a clean repo.
|
|
7249
|
-
...gitignoreSkipped > 0 ? { gitignoreSkipped } : {},
|
|
7250
|
-
...baseline ? { baseline } : {}
|
|
7251
|
-
};
|
|
7252
|
-
return { results: reported3, summary, frame };
|
|
7253
|
-
}
|
|
7254
|
-
|
|
7255
4300
|
export {
|
|
7256
4301
|
collect,
|
|
7257
4302
|
splitList,
|
|
@@ -7279,6 +4324,7 @@ export {
|
|
|
7279
4324
|
META_CONTEXT,
|
|
7280
4325
|
EncryptionRefusal,
|
|
7281
4326
|
unreadableMessage,
|
|
4327
|
+
unverifiedWarning,
|
|
7282
4328
|
lazyKey,
|
|
7283
4329
|
pointerOf,
|
|
7284
4330
|
topLevelKeyOf,
|
|
@@ -7287,14 +4333,17 @@ export {
|
|
|
7287
4333
|
withValueAt,
|
|
7288
4334
|
encryptionView,
|
|
7289
4335
|
settleFindings,
|
|
4336
|
+
encryptionFindings,
|
|
7290
4337
|
redactForModel,
|
|
7291
4338
|
redactedDerived,
|
|
7292
4339
|
EXTERNAL_OWNED_SCHEMA,
|
|
7293
4340
|
EXTERNAL_DUPLICATE_SCHEMA,
|
|
7294
4341
|
EXTERNAL_KEYWORD,
|
|
7295
4342
|
PATH_JOIN,
|
|
4343
|
+
reportedPath,
|
|
7296
4344
|
externalMetadataJoin,
|
|
7297
4345
|
loadExternalMetadata,
|
|
4346
|
+
outsideRefusal,
|
|
7298
4347
|
mergeExternalMetadata,
|
|
7299
4348
|
mergeWithMarks,
|
|
7300
4349
|
externalMetadataPointer,
|
|
@@ -7305,6 +4354,7 @@ export {
|
|
|
7305
4354
|
LOCATION_KEYWORD,
|
|
7306
4355
|
LOCATION_EXTERNAL_SCHEMA,
|
|
7307
4356
|
LOCATION_PAGE_SCHEMA,
|
|
4357
|
+
LOCATION_FINDING_KEYWORD,
|
|
7308
4358
|
GRAPH_OUTPUT_KEYWORD,
|
|
7309
4359
|
compileWithFormats,
|
|
7310
4360
|
stringFormatNames,
|
|
@@ -7319,6 +4369,7 @@ export {
|
|
|
7319
4369
|
familyMarks,
|
|
7320
4370
|
SYSTEM_COLUMNS,
|
|
7321
4371
|
RESERVED,
|
|
4372
|
+
quoteIdent,
|
|
7322
4373
|
bindValue,
|
|
7323
4374
|
corpusDataColumns,
|
|
7324
4375
|
createDocsTable,
|
|
@@ -7366,57 +4417,19 @@ export {
|
|
|
7366
4417
|
toFileLines,
|
|
7367
4418
|
toBodyLines,
|
|
7368
4419
|
splitPageArgument,
|
|
4420
|
+
ZERO_SHA,
|
|
4421
|
+
DEFAULT_MACHINES,
|
|
7369
4422
|
provenanceEntries,
|
|
7370
4423
|
readProvenancePage,
|
|
4424
|
+
parseLinePorcelain,
|
|
4425
|
+
machineIdentity,
|
|
4426
|
+
deriveProvenance,
|
|
7371
4427
|
compareProvenance,
|
|
7372
4428
|
planProvenanceWrite,
|
|
7373
4429
|
rangeResults,
|
|
7374
4430
|
provenanceFindings,
|
|
7375
4431
|
renderProvenanceValue,
|
|
7376
4432
|
parseProvenanceTarget,
|
|
7377
|
-
|
|
7378
|
-
machinesOf,
|
|
7379
|
-
REVIEW_CACHE_DIR,
|
|
7380
|
-
REVIEW_CACHE_VERSION,
|
|
7381
|
-
ReviewCache,
|
|
7382
|
-
cachedClient,
|
|
7383
|
-
BinMissing,
|
|
7384
|
-
run,
|
|
7385
|
-
PATH_PLACEHOLDER,
|
|
7386
|
-
isPerFile,
|
|
7387
|
-
argvFor,
|
|
7388
|
-
valueOf,
|
|
7389
|
-
deriveFromCommands,
|
|
7390
|
-
parseOriginUrl,
|
|
7391
|
-
identityFromOrigin,
|
|
7392
|
-
GitHubClient,
|
|
7393
|
-
GitLabClient,
|
|
7394
|
-
createReviewClient,
|
|
7395
|
-
deriveFromReviews,
|
|
7396
|
-
mapConcurrent,
|
|
7397
|
-
provenanceFenced,
|
|
7398
|
-
FIELD_SOURCES,
|
|
7399
|
-
sourcesFor,
|
|
7400
|
-
consultedSources,
|
|
7401
|
-
assertSourcesAvailable,
|
|
7402
|
-
deriveMetadata,
|
|
7403
|
-
DERIVED_VIEW,
|
|
7404
|
-
DERIVED_ROWS,
|
|
7405
|
-
RESOLVED_VIEW,
|
|
7406
|
-
derivedColumns,
|
|
7407
|
-
mentionsDerived,
|
|
7408
|
-
fieldsForSql,
|
|
7409
|
-
createDerivedView,
|
|
7410
|
-
resolvedColumns,
|
|
7411
|
-
mentionsResolved,
|
|
7412
|
-
createResolvedView,
|
|
7413
|
-
deriveForTable,
|
|
7414
|
-
rowsToFindings,
|
|
7415
|
-
provenanceManifests,
|
|
7416
|
-
provenancePlace,
|
|
7417
|
-
readerManifests,
|
|
7418
|
-
readerPlace,
|
|
7419
|
-
stampManifests,
|
|
7420
|
-
runValidate
|
|
4433
|
+
attributeRange
|
|
7421
4434
|
};
|
|
7422
|
-
//# sourceMappingURL=chunk-
|
|
4435
|
+
//# sourceMappingURL=chunk-GV56WGL2.js.map
|