@hawkeyexl/manni 4.5.0 → 4.7.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-U6S2JUUN.js +41 -0
- package/dist/build-4RN6WBCP.js +32 -0
- package/dist/calibrate-6VIZYR4L.js +36 -0
- package/dist/capture-DP3NXAZE.js +34 -0
- package/dist/{check-RQIIIURI.js → check-ERVNGKLC.js} +13 -14
- package/dist/check-IWHUPAZM.js +886 -0
- package/dist/check-IWHUPAZM.js.map +1 -0
- package/dist/{check-ESNS7ZBC.js → check-OAJWC3WD.js} +7 -5
- package/dist/check-STHXZIZO.js +26 -0
- package/dist/{chunk-TA42STP7.js → chunk-2ZWTSRE5.js} +4 -4
- package/dist/{chunk-F2OVHJNC.js → chunk-4Z2MN5FR.js} +11 -11
- package/dist/{chunk-HVFR66GQ.js → chunk-5UB47BEV.js} +3280 -283
- package/dist/chunk-5UB47BEV.js.map +1 -0
- package/dist/{chunk-TCC737IS.js → chunk-5VWDIWOH.js} +4 -4
- package/dist/{chunk-B7XUEQF3.js → chunk-6KKZXB2O.js} +7 -5
- package/dist/{chunk-B7XUEQF3.js.map → chunk-6KKZXB2O.js.map} +1 -1
- package/dist/{chunk-ELZ2IIOF.js → chunk-6URSCTTX.js} +2 -2
- package/dist/{chunk-K3U5F2QT.js → chunk-6VDBP5UV.js} +12 -12
- package/dist/chunk-7EL5LLKD.js +233 -0
- package/dist/chunk-7EL5LLKD.js.map +1 -0
- package/dist/{chunk-C3KI45RF.js → chunk-7XZ6INKO.js} +4 -4
- package/dist/{chunk-ZX43SRZR.js → chunk-AMEILOVV.js} +8 -6
- package/dist/{chunk-ZX43SRZR.js.map → chunk-AMEILOVV.js.map} +1 -1
- package/dist/{chunk-SNEHX4VE.js → chunk-B5CMM57M.js} +2 -2
- package/dist/chunk-BWCWTHYS.js +146 -0
- package/dist/chunk-BWCWTHYS.js.map +1 -0
- package/dist/{chunk-QLUMKWQQ.js → chunk-C2TJG5KV.js} +10 -6
- package/dist/{chunk-QLUMKWQQ.js.map → chunk-C2TJG5KV.js.map} +1 -1
- package/dist/{chunk-GAZFKZHM.js → chunk-CEVU3GFF.js} +6 -6
- package/dist/chunk-CI55ABCK.js +502 -0
- package/dist/chunk-CI55ABCK.js.map +1 -0
- package/dist/{chunk-QCBICNKC.js → chunk-CR3HZERW.js} +202 -54
- package/dist/chunk-CR3HZERW.js.map +1 -0
- package/dist/chunk-CU57HT2E.js +116 -0
- package/dist/chunk-CU57HT2E.js.map +1 -0
- package/dist/chunk-CWXZ2MRT.js +222 -0
- package/dist/chunk-CWXZ2MRT.js.map +1 -0
- package/dist/chunk-D2LBN44V.js +134 -0
- package/dist/chunk-D2LBN44V.js.map +1 -0
- package/dist/chunk-D6RO54QD.js +674 -0
- package/dist/chunk-D6RO54QD.js.map +1 -0
- package/dist/{chunk-52PDIZKV.js → chunk-ECVQNGTH.js} +3 -3
- package/dist/chunk-FQGLRJGX.js +174 -0
- package/dist/chunk-FQGLRJGX.js.map +1 -0
- package/dist/{chunk-NGK3N6F2.js → chunk-GCC52YZD.js} +3 -3
- package/dist/chunk-HX44GB4Z.js +30 -0
- package/dist/chunk-HX44GB4Z.js.map +1 -0
- package/dist/{chunk-NN4ASVTY.js → chunk-IIFADWWI.js} +4 -4
- package/dist/{chunk-OIZ3XMKB.js → chunk-IOL74S4J.js} +4 -4
- package/dist/{chunk-UK2I3ACY.js → chunk-IZBBXKMJ.js} +4 -4
- package/dist/{chunk-YA63PVWI.js → chunk-K52CS4KT.js} +12 -10
- package/dist/{chunk-YA63PVWI.js.map → chunk-K52CS4KT.js.map} +1 -1
- package/dist/{chunk-7OQAU4EA.js → chunk-LDKIIZDW.js} +16 -12
- package/dist/{chunk-7OQAU4EA.js.map → chunk-LDKIIZDW.js.map} +1 -1
- package/dist/{chunk-QY3DOZMS.js → chunk-LONRQBGV.js} +4 -4
- package/dist/{chunk-EL2BZTDL.js → chunk-LXJEAV2V.js} +13 -11
- package/dist/{chunk-EL2BZTDL.js.map → chunk-LXJEAV2V.js.map} +1 -1
- package/dist/{chunk-XWFNO5PO.js → chunk-MIBWI5H7.js} +3 -3
- package/dist/{chunk-527HHQPS.js → chunk-MSMB6XOS.js} +6 -4
- package/dist/{chunk-527HHQPS.js.map → chunk-MSMB6XOS.js.map} +1 -1
- package/dist/{chunk-C6HZ6C5T.js → chunk-NIFEEIGQ.js} +2 -2
- package/dist/{chunk-JQU7FHYH.js → chunk-NZ6TNSCU.js} +10 -10
- package/dist/{chunk-3R5Y3XOL.js → chunk-OOEMKOX6.js} +29 -591
- package/dist/chunk-OOEMKOX6.js.map +1 -0
- package/dist/{chunk-MZC62HPZ.js → chunk-PAOVVHQX.js} +2 -2
- package/dist/chunk-PNX43DYW.js +849 -0
- package/dist/chunk-PNX43DYW.js.map +1 -0
- package/dist/chunk-QCHVR4ZM.js +286 -0
- package/dist/chunk-QCHVR4ZM.js.map +1 -0
- package/dist/{chunk-SJO2FTPG.js → chunk-QP5X66TO.js} +73 -1304
- package/dist/chunk-QP5X66TO.js.map +1 -0
- package/dist/{chunk-HQFVJDEN.js → chunk-QQNPVW3J.js} +19 -19
- package/dist/chunk-TPVOOTMN.js +532 -0
- package/dist/chunk-TPVOOTMN.js.map +1 -0
- package/dist/{chunk-IA7BMIPE.js → chunk-UZKQSWHJ.js} +2 -2
- package/dist/chunk-WQWKYAAK.js +86 -0
- package/dist/chunk-WQWKYAAK.js.map +1 -0
- package/dist/{chunk-CP4ZM43V.js → chunk-WTEILBKC.js} +2 -2
- package/dist/{chunk-LJ2RDH3F.js → chunk-XG6PVB5S.js} +1 -23
- package/dist/chunk-XG6PVB5S.js.map +1 -0
- package/dist/{chunk-Q35YIDBV.js → chunk-XKBEHKWO.js} +29 -264
- package/dist/chunk-XKBEHKWO.js.map +1 -0
- package/dist/{chunk-BOIF2XGG.js → chunk-XXDXIIQ4.js} +2 -2
- package/dist/{chunk-6XTQDFGP.js → chunk-YSCZL4U5.js} +3 -3
- package/dist/{chunk-6XTQDFGP.js.map → chunk-YSCZL4U5.js.map} +1 -1
- package/dist/{chunk-7ZUCEMCY.js → chunk-ZEXGGGLW.js} +9 -11
- package/dist/{chunk-7ZUCEMCY.js.map → chunk-ZEXGGGLW.js.map} +1 -1
- package/dist/{chunk-DX32P2DT.js → chunk-ZKAKA54B.js} +2 -2
- package/dist/{chunk-TVIX653Q.js → chunk-ZKCNPKUO.js} +4 -4
- package/dist/{chunk-DE7JSWXV.js → chunk-ZNE25EAR.js} +8 -6
- package/dist/{chunk-DE7JSWXV.js.map → chunk-ZNE25EAR.js.map} +1 -1
- package/dist/{chunk-FLF6AECQ.js → chunk-ZPP75QWG.js} +17 -15
- package/dist/{chunk-FLF6AECQ.js.map → chunk-ZPP75QWG.js.map} +1 -1
- package/dist/{cli-PRZHQ3QB.js → cli-37SRAYNE.js} +14 -15
- package/dist/{cli-PRZHQ3QB.js.map → cli-37SRAYNE.js.map} +1 -1
- package/dist/{cli-TYGOHJOJ.js → cli-HXWYUKGJ.js} +225 -58
- package/dist/cli-HXWYUKGJ.js.map +1 -0
- package/dist/{cli-HUIMNJT6.js → cli-IRHPPUFJ.js} +19 -20
- package/dist/{cli-HUIMNJT6.js.map → cli-IRHPPUFJ.js.map} +1 -1
- package/dist/{cli-6PGVHP7Y.js → cli-KIID535Y.js} +17 -19
- package/dist/{cli-6PGVHP7Y.js.map → cli-KIID535Y.js.map} +1 -1
- package/dist/{cli-AAG3N4NB.js → cli-MTDGQIIV.js} +17 -18
- package/dist/{cli-AAG3N4NB.js.map → cli-MTDGQIIV.js.map} +1 -1
- package/dist/{cli-E3H6ZP4I.js → cli-NM6FZ54R.js} +5 -3
- package/dist/{cli-E3H6ZP4I.js.map → cli-NM6FZ54R.js.map} +1 -1
- package/dist/{cli-MHTYPVOD.js → cli-XIODOEUG.js} +113 -20
- package/dist/{cli-MHTYPVOD.js.map → cli-XIODOEUG.js.map} +1 -1
- package/dist/{cli-YW6VWBHX.js → cli-YL3IDR52.js} +10 -10
- package/dist/{cli-QKIJOXGW.js → cli-ZQUJYQBA.js} +17 -17
- package/dist/cli.js +11 -11
- package/dist/config-O6UBGEDK.js +26 -0
- package/dist/conformance-ALLPNGEQ.js +18 -0
- package/dist/docmeta.js +9 -9
- package/dist/github-UCYLM3DR.js +26 -0
- package/dist/github-VEJFO6JJ.js +31 -0
- package/dist/host-6GURH5HE.js +23 -0
- package/dist/index.d.ts +72 -4
- package/dist/index.js +176 -166
- package/dist/index.js.map +1 -1
- package/dist/{json-4DCKDGUV.js → json-6F52FSXZ.js} +12 -13
- package/dist/lint-X2GNINK6.js +29 -0
- package/dist/list-DP3O3CWB.js +30 -0
- package/dist/list-P77RZFZF.js +28 -0
- package/dist/{page-QEDB7EVU.js → page-WQCMBNRD.js} +11 -12
- package/dist/prepare-52QOK7M4.js +155 -0
- package/dist/prepare-52QOK7M4.js.map +1 -0
- package/dist/{pretty-SQX33UM2.js → pretty-5MMMNSWF.js} +13 -14
- package/dist/{pretty-5DGEBZSV.js → pretty-H4DQREMW.js} +12 -13
- package/dist/provider-AMGRDY6V.js +34 -0
- package/dist/release-CTYRHZL4.js +36 -0
- package/dist/release-CTYRHZL4.js.map +1 -0
- package/dist/{reporters-YELDDYJD.js → reporters-SU3VUKKX.js} +7 -5
- package/dist/{reporters-OHSI3RMV.js → reporters-WIHBEMPG.js} +11 -12
- package/dist/reporters-WIHBEMPG.js.map +1 -0
- package/dist/run-L2DWJ26K.js +32 -0
- package/dist/run-L2DWJ26K.js.map +1 -0
- package/dist/{term-2WMDZ4PY.js → term-PJIJQZNF.js} +16 -17
- package/dist/{term-2WMDZ4PY.js.map → term-PJIJQZNF.js.map} +1 -1
- package/dist/validate-XCDSPURG.js +17 -0
- package/dist/validate-XCDSPURG.js.map +1 -0
- package/package.json +2 -2
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/plugin/manni/hooks/hooks.json +47 -0
- package/dist/batch-GOE3UXOD.js +0 -36
- package/dist/build-M7BMEHL2.js +0 -33
- package/dist/calibrate-6RRAILUY.js +0 -31
- package/dist/capture-RJPQ6B4N.js +0 -32
- package/dist/check-BQD64X3N.js +0 -27
- package/dist/chunk-3R5Y3XOL.js.map +0 -1
- package/dist/chunk-3YHAYXBC.js +0 -164
- package/dist/chunk-3YHAYXBC.js.map +0 -1
- package/dist/chunk-HVFR66GQ.js.map +0 -1
- package/dist/chunk-LJ2RDH3F.js.map +0 -1
- package/dist/chunk-NAZRLJAK.js +0 -3090
- package/dist/chunk-NAZRLJAK.js.map +0 -1
- package/dist/chunk-Q35YIDBV.js.map +0 -1
- package/dist/chunk-QCBICNKC.js.map +0 -1
- package/dist/chunk-SJO2FTPG.js.map +0 -1
- package/dist/chunk-TU2WUBVZ.js +0 -2
- package/dist/cli-TYGOHJOJ.js.map +0 -1
- package/dist/github-3WMSTNTK.js +0 -32
- package/dist/github-OXSJQ4PS.js +0 -27
- package/dist/lint-YDHVVZHT.js +0 -30
- package/dist/list-3ZO2NIYO.js +0 -31
- package/dist/list-EJH6VAOW.js +0 -29
- package/dist/run-IP6NZHAG.js +0 -33
- package/dist/validate-G6HH3YXL.js +0 -16
- /package/dist/{batch-GOE3UXOD.js.map → batch-U6S2JUUN.js.map} +0 -0
- /package/dist/{build-M7BMEHL2.js.map → build-4RN6WBCP.js.map} +0 -0
- /package/dist/{calibrate-6RRAILUY.js.map → calibrate-6VIZYR4L.js.map} +0 -0
- /package/dist/{capture-RJPQ6B4N.js.map → capture-DP3NXAZE.js.map} +0 -0
- /package/dist/{check-BQD64X3N.js.map → check-ERVNGKLC.js.map} +0 -0
- /package/dist/{check-ESNS7ZBC.js.map → check-OAJWC3WD.js.map} +0 -0
- /package/dist/{check-RQIIIURI.js.map → check-STHXZIZO.js.map} +0 -0
- /package/dist/{chunk-TA42STP7.js.map → chunk-2ZWTSRE5.js.map} +0 -0
- /package/dist/{chunk-F2OVHJNC.js.map → chunk-4Z2MN5FR.js.map} +0 -0
- /package/dist/{chunk-TCC737IS.js.map → chunk-5VWDIWOH.js.map} +0 -0
- /package/dist/{chunk-ELZ2IIOF.js.map → chunk-6URSCTTX.js.map} +0 -0
- /package/dist/{chunk-K3U5F2QT.js.map → chunk-6VDBP5UV.js.map} +0 -0
- /package/dist/{chunk-C3KI45RF.js.map → chunk-7XZ6INKO.js.map} +0 -0
- /package/dist/{chunk-SNEHX4VE.js.map → chunk-B5CMM57M.js.map} +0 -0
- /package/dist/{chunk-GAZFKZHM.js.map → chunk-CEVU3GFF.js.map} +0 -0
- /package/dist/{chunk-52PDIZKV.js.map → chunk-ECVQNGTH.js.map} +0 -0
- /package/dist/{chunk-NGK3N6F2.js.map → chunk-GCC52YZD.js.map} +0 -0
- /package/dist/{chunk-NN4ASVTY.js.map → chunk-IIFADWWI.js.map} +0 -0
- /package/dist/{chunk-OIZ3XMKB.js.map → chunk-IOL74S4J.js.map} +0 -0
- /package/dist/{chunk-UK2I3ACY.js.map → chunk-IZBBXKMJ.js.map} +0 -0
- /package/dist/{chunk-QY3DOZMS.js.map → chunk-LONRQBGV.js.map} +0 -0
- /package/dist/{chunk-XWFNO5PO.js.map → chunk-MIBWI5H7.js.map} +0 -0
- /package/dist/{chunk-C6HZ6C5T.js.map → chunk-NIFEEIGQ.js.map} +0 -0
- /package/dist/{chunk-JQU7FHYH.js.map → chunk-NZ6TNSCU.js.map} +0 -0
- /package/dist/{chunk-MZC62HPZ.js.map → chunk-PAOVVHQX.js.map} +0 -0
- /package/dist/{chunk-HQFVJDEN.js.map → chunk-QQNPVW3J.js.map} +0 -0
- /package/dist/{chunk-IA7BMIPE.js.map → chunk-UZKQSWHJ.js.map} +0 -0
- /package/dist/{chunk-CP4ZM43V.js.map → chunk-WTEILBKC.js.map} +0 -0
- /package/dist/{chunk-BOIF2XGG.js.map → chunk-XXDXIIQ4.js.map} +0 -0
- /package/dist/{chunk-DX32P2DT.js.map → chunk-ZKAKA54B.js.map} +0 -0
- /package/dist/{chunk-TVIX653Q.js.map → chunk-ZKCNPKUO.js.map} +0 -0
- /package/dist/{chunk-TU2WUBVZ.js.map → cli-YL3IDR52.js.map} +0 -0
- /package/dist/{cli-QKIJOXGW.js.map → cli-ZQUJYQBA.js.map} +0 -0
- /package/dist/{cli-YW6VWBHX.js.map → config-O6UBGEDK.js.map} +0 -0
- /package/dist/{github-3WMSTNTK.js.map → conformance-ALLPNGEQ.js.map} +0 -0
- /package/dist/{github-OXSJQ4PS.js.map → github-UCYLM3DR.js.map} +0 -0
- /package/dist/{json-4DCKDGUV.js.map → github-VEJFO6JJ.js.map} +0 -0
- /package/dist/{lint-YDHVVZHT.js.map → host-6GURH5HE.js.map} +0 -0
- /package/dist/{list-3ZO2NIYO.js.map → json-6F52FSXZ.js.map} +0 -0
- /package/dist/{list-EJH6VAOW.js.map → lint-X2GNINK6.js.map} +0 -0
- /package/dist/{page-QEDB7EVU.js.map → list-DP3O3CWB.js.map} +0 -0
- /package/dist/{pretty-5DGEBZSV.js.map → list-P77RZFZF.js.map} +0 -0
- /package/dist/{pretty-SQX33UM2.js.map → page-WQCMBNRD.js.map} +0 -0
- /package/dist/{reporters-OHSI3RMV.js.map → pretty-5MMMNSWF.js.map} +0 -0
- /package/dist/{reporters-YELDDYJD.js.map → pretty-H4DQREMW.js.map} +0 -0
- /package/dist/{run-IP6NZHAG.js.map → provider-AMGRDY6V.js.map} +0 -0
- /package/dist/{validate-G6HH3YXL.js.map → reporters-SU3VUKKX.js.map} +0 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CHECK_NAME,
|
|
4
|
+
DEFAULT_BASELINE_PATH,
|
|
3
5
|
DERIVED_KEYWORD,
|
|
4
6
|
DERIVED_STALE_SCHEMA,
|
|
7
|
+
DERIVE_SOURCES,
|
|
5
8
|
DocmetaError,
|
|
6
9
|
FILE_SCHEMA_KEY,
|
|
10
|
+
LEGACY_BASELINE_PATH,
|
|
7
11
|
ManifestCache,
|
|
12
|
+
PROVENANCE_FIELD,
|
|
13
|
+
STAMPED_DATE_FIELDS,
|
|
8
14
|
STDIN_LABEL,
|
|
9
15
|
STDIN_TOKEN,
|
|
16
|
+
assertNonEmpty,
|
|
10
17
|
blockLines,
|
|
18
|
+
canonicalSchemaRef,
|
|
11
19
|
classifyRef,
|
|
12
20
|
colOf,
|
|
13
21
|
collectSchemaPins,
|
|
14
22
|
colonEnd,
|
|
23
|
+
compareDerived,
|
|
15
24
|
contentEnd,
|
|
16
25
|
deepEqual,
|
|
26
|
+
derivableFields,
|
|
27
|
+
deriveCovers,
|
|
17
28
|
detectEol,
|
|
18
29
|
detectStyle,
|
|
19
30
|
emptySource,
|
|
@@ -24,25 +35,34 @@ import {
|
|
|
24
35
|
gitignoreOptions,
|
|
25
36
|
insertKey,
|
|
26
37
|
integrityOf,
|
|
38
|
+
isBuiltinField,
|
|
39
|
+
isErrorSeverity,
|
|
27
40
|
keyLines,
|
|
28
41
|
keyString,
|
|
29
42
|
lineEnd,
|
|
30
43
|
loadConfig,
|
|
31
44
|
loadSchema,
|
|
32
45
|
locateFrontmatter,
|
|
46
|
+
managedFields,
|
|
33
47
|
positionForFactory,
|
|
34
48
|
publishedBuiltins,
|
|
35
49
|
rangeOf,
|
|
36
50
|
rebaseConfigSchemaRefs,
|
|
37
51
|
replaceBlockValue,
|
|
38
52
|
replaceFlowValue,
|
|
53
|
+
resolveBaselineRequest,
|
|
39
54
|
resolveElements,
|
|
55
|
+
resolveRunConfig,
|
|
40
56
|
resolveSchemaSetWithSource,
|
|
41
57
|
resolveTargetSet,
|
|
42
58
|
schemaLoadOptions,
|
|
43
59
|
schemaTrustRoot,
|
|
60
|
+
settleBaseline,
|
|
61
|
+
staleFindings,
|
|
62
|
+
supportedExtensions,
|
|
63
|
+
toJsonText,
|
|
44
64
|
writeFileAtomic
|
|
45
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-MSMB6XOS.js";
|
|
46
66
|
import {
|
|
47
67
|
STDIN_LINES_MARKER
|
|
48
68
|
} from "./chunk-YEUYKR62.js";
|
|
@@ -67,12 +87,106 @@ import {
|
|
|
67
87
|
pageManifestPath,
|
|
68
88
|
parseCollections,
|
|
69
89
|
sameFile
|
|
70
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-XKBEHKWO.js";
|
|
71
91
|
import {
|
|
72
92
|
ToolError,
|
|
73
93
|
errorMessage
|
|
74
94
|
} from "./chunk-G3T47CFV.js";
|
|
75
95
|
|
|
96
|
+
// src/meta/commands/validate.ts
|
|
97
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
98
|
+
|
|
99
|
+
// src/meta/core/external-metadata.ts
|
|
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
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function rebase(held, current, codec) {
|
|
139
|
+
for (const base of held.bases.values()) {
|
|
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;
|
|
145
|
+
}
|
|
146
|
+
function rebaseline(held) {
|
|
147
|
+
held.before = held.text;
|
|
148
|
+
held.ops = [];
|
|
149
|
+
held.bases.clear();
|
|
150
|
+
held.absent = false;
|
|
151
|
+
}
|
|
152
|
+
async function settle(held, io) {
|
|
153
|
+
for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
|
|
154
|
+
let current;
|
|
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));
|
|
177
|
+
}
|
|
178
|
+
function isMissing(error) {
|
|
179
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
180
|
+
}
|
|
181
|
+
async function readManifestOrEmpty(absPath, perPage) {
|
|
182
|
+
try {
|
|
183
|
+
return await readFile(absPath, "utf8");
|
|
184
|
+
} catch (e) {
|
|
185
|
+
if (perPage && isMissing(e)) return "";
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
76
190
|
// src/meta/core/external-metadata-fetch.ts
|
|
77
191
|
var EXTERNAL_METADATA_FETCH_TIMEOUT_MS = 1e4;
|
|
78
192
|
var EXTERNAL_METADATA_FETCH_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -166,7 +280,7 @@ async function readCapped(url, res, maxBytes) {
|
|
|
166
280
|
|
|
167
281
|
// src/shared/encryption-key.ts
|
|
168
282
|
import { spawnSync } from "node:child_process";
|
|
169
|
-
import { readFile } from "node:fs/promises";
|
|
283
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
170
284
|
import { basename as basename2, dirname as dirname2, join as join2, relative, resolve } from "node:path";
|
|
171
285
|
import { isDeepStrictEqual } from "node:util";
|
|
172
286
|
import { isMap, parseDocument, parse as parseYaml, stringify } from "yaml";
|
|
@@ -179,16 +293,16 @@ var RENAME_ATTEMPTS = 3;
|
|
|
179
293
|
function errorCode(err) {
|
|
180
294
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
181
295
|
}
|
|
182
|
-
var wait = (ms) => new Promise((
|
|
183
|
-
setTimeout(
|
|
296
|
+
var wait = (ms) => new Promise((resolve8) => {
|
|
297
|
+
setTimeout(resolve8, ms);
|
|
184
298
|
});
|
|
185
|
-
async function writeTextAtomic(path,
|
|
299
|
+
async function writeTextAtomic(path, text2) {
|
|
186
300
|
const tmp = join(
|
|
187
301
|
dirname(path),
|
|
188
302
|
`.${basename(path)}.manni-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`
|
|
189
303
|
);
|
|
190
304
|
try {
|
|
191
|
-
await writeFile(tmp,
|
|
305
|
+
await writeFile(tmp, text2, "utf8");
|
|
192
306
|
try {
|
|
193
307
|
const { mode } = await stat(path);
|
|
194
308
|
await chmod(tmp, mode);
|
|
@@ -236,7 +350,7 @@ function errorCode2(err) {
|
|
|
236
350
|
}
|
|
237
351
|
async function readIfPresent(path) {
|
|
238
352
|
try {
|
|
239
|
-
return await
|
|
353
|
+
return await readFile2(path, "utf8");
|
|
240
354
|
} catch (err) {
|
|
241
355
|
if (errorCode2(err) === "ENOENT") return null;
|
|
242
356
|
throw err;
|
|
@@ -248,21 +362,21 @@ async function fileTarget(file, toError) {
|
|
|
248
362
|
`${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}.`
|
|
249
363
|
);
|
|
250
364
|
}
|
|
251
|
-
const
|
|
252
|
-
return { path: file.path, source: file.source, text:
|
|
365
|
+
const text2 = await readIfPresent(file.path);
|
|
366
|
+
return { path: file.path, source: file.source, text: text2 ?? file.text };
|
|
253
367
|
}
|
|
254
368
|
async function defaultTarget(cwd) {
|
|
255
369
|
const root = findGitRoot(cwd) ?? resolve(cwd);
|
|
256
370
|
for (const name of FAMILY_CONFIG_NAMES) {
|
|
257
371
|
const path2 = join2(root, name);
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
372
|
+
const text2 = await readIfPresent(path2);
|
|
373
|
+
if (text2 !== null) return { path: path2, source: relativeSource(cwd, path2), text: text2 };
|
|
260
374
|
}
|
|
261
375
|
const path = join2(root, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml");
|
|
262
376
|
return { path, source: relativeSource(cwd, path), text: null };
|
|
263
377
|
}
|
|
264
|
-
function withKey(
|
|
265
|
-
const doc = parseDocument(
|
|
378
|
+
function withKey(text2, key, previous, source, toError) {
|
|
379
|
+
const doc = parseDocument(text2);
|
|
266
380
|
const [first] = doc.errors;
|
|
267
381
|
if (first !== void 0) {
|
|
268
382
|
throw toError(`${source}: invalid YAML: ${first.message}`);
|
|
@@ -307,12 +421,12 @@ async function writeEncryptionKey(opts) {
|
|
|
307
421
|
);
|
|
308
422
|
}
|
|
309
423
|
const target = file === null ? await defaultTarget(opts.cwd) : await fileTarget(file, toError);
|
|
310
|
-
const
|
|
424
|
+
const text2 = target.text === null ? stringify({
|
|
311
425
|
[ENCRYPTION_KEY_FIELD]: key,
|
|
312
426
|
...typeof previous === "string" ? { [ENCRYPTION_KEY_PREVIOUS_FIELD]: previous } : {}
|
|
313
427
|
}) : withKey(target.text, key, previous, target.source, toError);
|
|
314
|
-
verify(target.text,
|
|
315
|
-
if (opts.dryRun !== true) await writeTextAtomic(target.path,
|
|
428
|
+
verify(target.text, text2, key, previous, target.source, toError);
|
|
429
|
+
if (opts.dryRun !== true) await writeTextAtomic(target.path, text2);
|
|
316
430
|
return { path: target.path, source: target.source, created: target.text === null };
|
|
317
431
|
}
|
|
318
432
|
function isIgnoredByGit(path) {
|
|
@@ -377,8 +491,8 @@ function valueAt(data, pointer) {
|
|
|
377
491
|
let node = data;
|
|
378
492
|
for (const seg of segments(pointer)) {
|
|
379
493
|
if (Array.isArray(node)) {
|
|
380
|
-
const
|
|
381
|
-
node = /^\d+$/.test(seg) ?
|
|
494
|
+
const items2 = node;
|
|
495
|
+
node = /^\d+$/.test(seg) ? items2[Number(seg)] : void 0;
|
|
382
496
|
} else if (isRecord(node)) {
|
|
383
497
|
node = Object.hasOwn(node, seg) ? node[seg] : void 0;
|
|
384
498
|
} else {
|
|
@@ -393,10 +507,10 @@ function withValueAt(root, pointer, value) {
|
|
|
393
507
|
const seg = parts[i];
|
|
394
508
|
if (seg === void 0) return value;
|
|
395
509
|
if (Array.isArray(node)) {
|
|
396
|
-
const
|
|
397
|
-
if (!/^\d+$/.test(seg) || Number(seg) >=
|
|
398
|
-
|
|
399
|
-
return
|
|
510
|
+
const items2 = [...node];
|
|
511
|
+
if (!/^\d+$/.test(seg) || Number(seg) >= items2.length) return node;
|
|
512
|
+
items2[Number(seg)] = set(items2[Number(seg)], i + 1);
|
|
513
|
+
return items2;
|
|
400
514
|
}
|
|
401
515
|
if (isRecord(node) && Object.hasOwn(node, seg)) {
|
|
402
516
|
return { ...node, [seg]: set(node[seg], i + 1) };
|
|
@@ -533,97 +647,6 @@ function redactedDerived(f) {
|
|
|
533
647
|
};
|
|
534
648
|
}
|
|
535
649
|
|
|
536
|
-
// src/meta/core/external-metadata.ts
|
|
537
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
538
|
-
import { isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
539
|
-
import {
|
|
540
|
-
LineCounter,
|
|
541
|
-
isMap as isMap2,
|
|
542
|
-
isNode,
|
|
543
|
-
isScalar,
|
|
544
|
-
isSeq,
|
|
545
|
-
parseDocument as parseDocument2
|
|
546
|
-
} from "yaml";
|
|
547
|
-
|
|
548
|
-
// src/shared/manifest-cas.ts
|
|
549
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
550
|
-
import { posix } from "node:path";
|
|
551
|
-
var COMMIT_ATTEMPTS = 5;
|
|
552
|
-
function conflictRefusal(file) {
|
|
553
|
-
return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
|
|
554
|
-
}
|
|
555
|
-
function heldManifest(path, file, before, absent = false) {
|
|
556
|
-
return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
|
|
557
|
-
}
|
|
558
|
-
function encodeValue(value) {
|
|
559
|
-
return value === void 0 ? "absent" : JSON.stringify(value);
|
|
560
|
-
}
|
|
561
|
-
function baseKey(at2) {
|
|
562
|
-
const entry = at2.join === "path" ? posix.normalize(at2.entry) : at2.entry;
|
|
563
|
-
return `${entry}\0${at2.key}`;
|
|
564
|
-
}
|
|
565
|
-
function rebaseable(held, op, codec) {
|
|
566
|
-
const at2 = baseKey(op);
|
|
567
|
-
if (held.bases.has(at2)) return;
|
|
568
|
-
held.bases.set(at2, {
|
|
569
|
-
entry: op.entry,
|
|
570
|
-
key: op.key,
|
|
571
|
-
join: op.join,
|
|
572
|
-
value: encodeValue(codec.read(held.before, op))
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
function rebase(held, current, codec) {
|
|
576
|
-
for (const base of held.bases.values()) {
|
|
577
|
-
if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
|
|
578
|
-
}
|
|
579
|
-
let text = current;
|
|
580
|
-
for (const op of held.ops) text = codec.apply(text, op);
|
|
581
|
-
return text;
|
|
582
|
-
}
|
|
583
|
-
function rebaseline(held) {
|
|
584
|
-
held.before = held.text;
|
|
585
|
-
held.ops = [];
|
|
586
|
-
held.bases.clear();
|
|
587
|
-
held.absent = false;
|
|
588
|
-
}
|
|
589
|
-
async function settle(held, io) {
|
|
590
|
-
for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
|
|
591
|
-
let current;
|
|
592
|
-
try {
|
|
593
|
-
current = await io.read(held.path);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
if (held.absent && isMissing(error)) {
|
|
596
|
-
await io.write(held.path, held.text);
|
|
597
|
-
held.absent = false;
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
601
|
-
throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
|
|
602
|
-
}
|
|
603
|
-
if (!held.absent && current === held.before) {
|
|
604
|
-
await io.write(held.path, held.text);
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
const replayed = rebase(held, current, io.codec);
|
|
608
|
-
if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
|
|
609
|
-
held.before = current;
|
|
610
|
-
held.text = replayed;
|
|
611
|
-
held.absent = false;
|
|
612
|
-
}
|
|
613
|
-
throw io.toError(conflictRefusal(held.file));
|
|
614
|
-
}
|
|
615
|
-
function isMissing(error) {
|
|
616
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
617
|
-
}
|
|
618
|
-
async function readManifestOrEmpty(absPath, perPage) {
|
|
619
|
-
try {
|
|
620
|
-
return await readFile2(absPath, "utf8");
|
|
621
|
-
} catch (e) {
|
|
622
|
-
if (perPage && isMissing(e)) return "";
|
|
623
|
-
throw e;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
650
|
// src/meta/core/external-metadata.ts
|
|
628
651
|
var EXTERNAL_OWNED_SCHEMA = "external:owned";
|
|
629
652
|
var EXTERNAL_DUPLICATE_SCHEMA = "external:duplicate";
|
|
@@ -666,18 +689,18 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
666
689
|
rules.push({ collection: declared, manifest, configDir: opts.configDir });
|
|
667
690
|
continue;
|
|
668
691
|
}
|
|
669
|
-
const
|
|
692
|
+
const join5 = externalMetadataJoin(manifest);
|
|
670
693
|
let parsed;
|
|
671
694
|
let file;
|
|
672
695
|
if (classifyRef(manifest.file).kind === "url") {
|
|
673
696
|
file = manifest.file;
|
|
674
|
-
const
|
|
697
|
+
const text2 = await fetchExternalMetadata(manifest.file, {
|
|
675
698
|
...manifest.tokenEnv !== void 0 ? { tokenEnv: manifest.tokenEnv } : {},
|
|
676
699
|
...opts.offline !== void 0 ? { offline: opts.offline } : {},
|
|
677
700
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
678
701
|
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
679
702
|
});
|
|
680
|
-
parsed = parseManifest(manifest, collection,
|
|
703
|
+
parsed = parseManifest(manifest, collection, text2, file, opts.configDir, join5, ownership(declared, manifest));
|
|
681
704
|
} else {
|
|
682
705
|
const abs = isAbsolute(manifest.file) ? manifest.file : resolve2(opts.configDir, manifest.file);
|
|
683
706
|
const label = reportedPath(abs, opts.base);
|
|
@@ -693,7 +716,7 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
693
716
|
// this run's spelling onto every value and entry instead.
|
|
694
717
|
JSON.stringify([
|
|
695
718
|
collection,
|
|
696
|
-
|
|
719
|
+
join5,
|
|
697
720
|
opts.configDir,
|
|
698
721
|
variantOf(declared, manifest)
|
|
699
722
|
]),
|
|
@@ -703,7 +726,7 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
703
726
|
await readManifest(abs, label),
|
|
704
727
|
label,
|
|
705
728
|
opts.configDir,
|
|
706
|
-
|
|
729
|
+
join5,
|
|
707
730
|
ownership(declared, manifest)
|
|
708
731
|
)
|
|
709
732
|
);
|
|
@@ -717,14 +740,14 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
717
740
|
else owners.set(key, [{ collection, file }]);
|
|
718
741
|
}
|
|
719
742
|
let target;
|
|
720
|
-
if (
|
|
743
|
+
if (join5 === PATH_JOIN) {
|
|
721
744
|
target = byPath;
|
|
722
745
|
} else {
|
|
723
|
-
const existing = byField.get(
|
|
746
|
+
const existing = byField.get(join5);
|
|
724
747
|
if (existing) target = existing;
|
|
725
748
|
else {
|
|
726
749
|
target = /* @__PURE__ */ new Map();
|
|
727
|
-
byField.set(
|
|
750
|
+
byField.set(join5, target);
|
|
728
751
|
}
|
|
729
752
|
}
|
|
730
753
|
for (const [indexKey, values] of parsed.values) {
|
|
@@ -759,14 +782,14 @@ function variantOf(declared, manifest) {
|
|
|
759
782
|
}
|
|
760
783
|
async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
761
784
|
const collection = declared.name;
|
|
762
|
-
const
|
|
785
|
+
const join5 = externalMetadataJoin(manifest);
|
|
763
786
|
const implied = manifest.keys === void 0 ? { collection: declared, manifest, file: manifest.file, files: /* @__PURE__ */ new Set() } : void 0;
|
|
764
787
|
for (const key of manifest.keys ?? []) {
|
|
765
788
|
const list2 = into.owners.get(key);
|
|
766
789
|
if (list2) list2.push({ collection, file: manifest.file });
|
|
767
790
|
else into.owners.set(key, [{ collection, file: manifest.file }]);
|
|
768
791
|
}
|
|
769
|
-
const target = indexFor(
|
|
792
|
+
const target = indexFor(join5, into);
|
|
770
793
|
for (const pageAbs of opts.pages ?? []) {
|
|
771
794
|
const resolved = pageManifestPath(manifest.file, opts.configDir, pageAbs);
|
|
772
795
|
if ("outside" in resolved) {
|
|
@@ -788,14 +811,14 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
788
811
|
const label = reportedPath(resolved.abs, opts.base);
|
|
789
812
|
const parsed = await parsedManifests.parse(
|
|
790
813
|
resolved.abs,
|
|
791
|
-
JSON.stringify([collection,
|
|
814
|
+
JSON.stringify([collection, join5, opts.configDir, variantOf(declared, manifest)]),
|
|
792
815
|
async () => parseManifest(
|
|
793
816
|
manifest,
|
|
794
817
|
collection,
|
|
795
818
|
await readPageManifest(resolved.abs, label),
|
|
796
819
|
label,
|
|
797
820
|
opts.configDir,
|
|
798
|
-
|
|
821
|
+
join5,
|
|
799
822
|
ownership(declared, manifest)
|
|
800
823
|
)
|
|
801
824
|
);
|
|
@@ -809,7 +832,7 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
809
832
|
);
|
|
810
833
|
}
|
|
811
834
|
for (const [indexKey, values] of parsed.values) {
|
|
812
|
-
const key =
|
|
835
|
+
const key = join5 === PATH_JOIN ? pageAbs : indexKey;
|
|
813
836
|
const merged = target.get(key) ?? /* @__PURE__ */ new Map();
|
|
814
837
|
for (const [owned, value] of values) merged.set(owned, reported(value, label));
|
|
815
838
|
target.set(key, merged);
|
|
@@ -823,12 +846,12 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
|
|
|
823
846
|
}
|
|
824
847
|
return implied;
|
|
825
848
|
}
|
|
826
|
-
function indexFor(
|
|
827
|
-
if (
|
|
828
|
-
const existing = into.byField.get(
|
|
849
|
+
function indexFor(join5, into) {
|
|
850
|
+
if (join5 === PATH_JOIN) return into.byPath;
|
|
851
|
+
const existing = into.byField.get(join5);
|
|
829
852
|
if (existing) return existing;
|
|
830
853
|
const created = /* @__PURE__ */ new Map();
|
|
831
|
-
into.byField.set(
|
|
854
|
+
into.byField.set(join5, created);
|
|
832
855
|
return created;
|
|
833
856
|
}
|
|
834
857
|
function outsideRefusal(manifest, collection, pageRel) {
|
|
@@ -858,11 +881,11 @@ function unreadable(file, err) {
|
|
|
858
881
|
const reason = err instanceof Error ? err.message : String(err);
|
|
859
882
|
return new DocmetaError(`Manifest ${file} could not be read: ${reason}`);
|
|
860
883
|
}
|
|
861
|
-
function parseManifest(manifest, collection,
|
|
884
|
+
function parseManifest(manifest, collection, text2, file, configDir, join5, owns) {
|
|
862
885
|
const target = /* @__PURE__ */ new Map();
|
|
863
886
|
const entries = [];
|
|
864
887
|
const lc = new LineCounter();
|
|
865
|
-
const doc = parseDocument2(
|
|
888
|
+
const doc = parseDocument2(text2, { lineCounter: lc, uniqueKeys: false });
|
|
866
889
|
const problem = doc.errors[0];
|
|
867
890
|
if (problem) {
|
|
868
891
|
throw new DocmetaError(
|
|
@@ -874,7 +897,7 @@ function parseManifest(manifest, collection, text, file, configDir, join4, owns)
|
|
|
874
897
|
return { values: target, entries };
|
|
875
898
|
if (!isMap2(root)) {
|
|
876
899
|
throw new DocmetaError(
|
|
877
|
-
`Manifest ${file}: the manifest must be a mapping from document ${
|
|
900
|
+
`Manifest ${file}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
|
|
878
901
|
);
|
|
879
902
|
}
|
|
880
903
|
const lineAt = (node) => {
|
|
@@ -898,7 +921,7 @@ function parseManifest(manifest, collection, text, file, configDir, join4, owns)
|
|
|
898
921
|
`Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
|
|
899
922
|
);
|
|
900
923
|
}
|
|
901
|
-
const indexKey =
|
|
924
|
+
const indexKey = join5 === PATH_JOIN ? resolve2(configDir, spelled) : spelled;
|
|
902
925
|
const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
|
|
903
926
|
for (const kv of pair.value.items) {
|
|
904
927
|
const key = isScalar(kv.key) ? String(kv.key.value) : String(kv.key);
|
|
@@ -924,8 +947,8 @@ function parseManifest(manifest, collection, text, file, configDir, join4, owns)
|
|
|
924
947
|
target.set(indexKey, values);
|
|
925
948
|
entries.push({
|
|
926
949
|
collection,
|
|
927
|
-
join:
|
|
928
|
-
...
|
|
950
|
+
join: join5,
|
|
951
|
+
...join5 === PATH_JOIN ? { abs: indexKey } : {},
|
|
929
952
|
spelled,
|
|
930
953
|
file,
|
|
931
954
|
...entryLine === void 0 ? {} : { line: entryLine }
|
|
@@ -1186,9 +1209,6 @@ function isFieldLocation(value) {
|
|
|
1186
1209
|
return value === "page" || value === "external";
|
|
1187
1210
|
}
|
|
1188
1211
|
|
|
1189
|
-
// src/meta/core/graph-output.ts
|
|
1190
|
-
var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
|
|
1191
|
-
|
|
1192
1212
|
// src/meta/core/validator.ts
|
|
1193
1213
|
import * as AjvDraft07Ns from "ajv";
|
|
1194
1214
|
import * as Ajv2019Ns from "ajv/dist/2019.js";
|
|
@@ -1196,6 +1216,11 @@ import * as Ajv2020Ns from "ajv/dist/2020.js";
|
|
|
1196
1216
|
import * as AjvDraft04Ns from "ajv-draft-04";
|
|
1197
1217
|
import * as addFormatsNs from "ajv-formats";
|
|
1198
1218
|
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
|
|
1199
1224
|
var draft06MetaSchema = createRequire(import.meta.url)(
|
|
1200
1225
|
"ajv/dist/refs/json-schema-draft-06.json"
|
|
1201
1226
|
);
|
|
@@ -1810,6 +1835,23 @@ function familyMarks(opts) {
|
|
|
1810
1835
|
})();
|
|
1811
1836
|
}
|
|
1812
1837
|
|
|
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
|
+
|
|
1813
1855
|
// src/meta/core/projection.ts
|
|
1814
1856
|
var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
|
|
1815
1857
|
var RESERVED = new Set(SYSTEM_COLUMNS);
|
|
@@ -1940,7 +1982,7 @@ function isParamName(name) {
|
|
|
1940
1982
|
return PARAM_NAME_HEAD.exec(name)?.[0] === name;
|
|
1941
1983
|
}
|
|
1942
1984
|
function collectNamedParameters(sql) {
|
|
1943
|
-
const
|
|
1985
|
+
const tokens2 = [];
|
|
1944
1986
|
const seen = /* @__PURE__ */ new Set();
|
|
1945
1987
|
const prefixOf = /* @__PURE__ */ new Map();
|
|
1946
1988
|
let i = 0;
|
|
@@ -1970,14 +2012,14 @@ function collectNamedParameters(sql) {
|
|
|
1970
2012
|
prefixOf.set(m[0], ch);
|
|
1971
2013
|
if (!seen.has(token)) {
|
|
1972
2014
|
seen.add(token);
|
|
1973
|
-
|
|
2015
|
+
tokens2.push(token);
|
|
1974
2016
|
}
|
|
1975
2017
|
i += 1 + m[0].length;
|
|
1976
2018
|
} else {
|
|
1977
2019
|
i++;
|
|
1978
2020
|
}
|
|
1979
2021
|
}
|
|
1980
|
-
return
|
|
2022
|
+
return tokens2;
|
|
1981
2023
|
}
|
|
1982
2024
|
function stripLeadingTrivia(sql) {
|
|
1983
2025
|
let i = 0;
|
|
@@ -1990,16 +2032,16 @@ function stripLeadingTrivia(sql) {
|
|
|
1990
2032
|
return sql.slice(i);
|
|
1991
2033
|
}
|
|
1992
2034
|
}
|
|
1993
|
-
function isTrivia(
|
|
2035
|
+
function isTrivia(text2) {
|
|
1994
2036
|
let i = 0;
|
|
1995
|
-
while (i <
|
|
1996
|
-
const ch =
|
|
2037
|
+
while (i < text2.length) {
|
|
2038
|
+
const ch = text2[i];
|
|
1997
2039
|
if (ch === void 0 || /\s/.test(ch) || ch === ";") {
|
|
1998
2040
|
i++;
|
|
1999
2041
|
continue;
|
|
2000
2042
|
}
|
|
2001
|
-
if (startsComment(
|
|
2002
|
-
i = skipComment(
|
|
2043
|
+
if (startsComment(text2, i)) {
|
|
2044
|
+
i = skipComment(text2, i);
|
|
2003
2045
|
continue;
|
|
2004
2046
|
}
|
|
2005
2047
|
return false;
|
|
@@ -2011,8 +2053,8 @@ function loadSqlite() {
|
|
|
2011
2053
|
sqliteModule ??= (async () => {
|
|
2012
2054
|
const original = process.emitWarning.bind(process);
|
|
2013
2055
|
const filtered = (warning, ...rest) => {
|
|
2014
|
-
const
|
|
2015
|
-
if (
|
|
2056
|
+
const text2 = typeof warning === "string" ? warning : warning.message;
|
|
2057
|
+
if (text2.includes("SQLite is an experimental feature")) return;
|
|
2016
2058
|
Reflect.apply(original, process, [warning, ...rest]);
|
|
2017
2059
|
};
|
|
2018
2060
|
process.emitWarning = filtered;
|
|
@@ -2026,7 +2068,6 @@ function loadSqlite() {
|
|
|
2026
2068
|
}
|
|
2027
2069
|
|
|
2028
2070
|
// src/meta/core/collections.ts
|
|
2029
|
-
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolvePath, sep as sep2 } from "node:path";
|
|
2030
2071
|
function memberOf(collections, configDir, base, label) {
|
|
2031
2072
|
if (collections.length === 0) return [];
|
|
2032
2073
|
if (label === STDIN_LABEL) return [];
|
|
@@ -2067,20 +2108,6 @@ function createCollectionViews(db, collections) {
|
|
|
2067
2108
|
}
|
|
2068
2109
|
}
|
|
2069
2110
|
|
|
2070
|
-
// src/meta/core/relocation.ts
|
|
2071
|
-
import { access, readFile as readFile4, stat as stat2, unlink } from "node:fs/promises";
|
|
2072
|
-
import { basename as basename3, extname, isAbsolute as isAbsolute3, join as join3, relative as relative4, resolve as resolve3, sep as sep3 } from "node:path";
|
|
2073
|
-
import picomatch from "picomatch";
|
|
2074
|
-
import {
|
|
2075
|
-
LineCounter as LineCounter3,
|
|
2076
|
-
isMap as isMap4,
|
|
2077
|
-
isNode as isNode3,
|
|
2078
|
-
isScalar as isScalar3,
|
|
2079
|
-
isSeq as isSeq2,
|
|
2080
|
-
parse as parseYaml2,
|
|
2081
|
-
parseDocument as parseDocument4
|
|
2082
|
-
} from "yaml";
|
|
2083
|
-
|
|
2084
2111
|
// src/meta/core/external-metadata-write.ts
|
|
2085
2112
|
import { posix as posix3 } from "node:path";
|
|
2086
2113
|
import {
|
|
@@ -2091,9 +2118,9 @@ import {
|
|
|
2091
2118
|
parseDocument as parseDocument3,
|
|
2092
2119
|
stringify as stringify2
|
|
2093
2120
|
} from "yaml";
|
|
2094
|
-
function spliceManifestValue(
|
|
2121
|
+
function spliceManifestValue(text2, options) {
|
|
2095
2122
|
const { entry, key, value, file } = options;
|
|
2096
|
-
const
|
|
2123
|
+
const join5 = options.join ?? PATH_JOIN;
|
|
2097
2124
|
const at2 = (line2) => {
|
|
2098
2125
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2099
2126
|
if (line2 === void 0) return name;
|
|
@@ -2107,9 +2134,9 @@ function spliceManifestValue(text, options) {
|
|
|
2107
2134
|
if (value === void 0) {
|
|
2108
2135
|
throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
|
|
2109
2136
|
}
|
|
2110
|
-
const eol = detectEol(
|
|
2137
|
+
const eol = detectEol(text2);
|
|
2111
2138
|
const lc = new LineCounter2();
|
|
2112
|
-
const doc = parseDocument3(
|
|
2139
|
+
const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
|
|
2113
2140
|
const problem = doc.errors[0];
|
|
2114
2141
|
if (problem) {
|
|
2115
2142
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2118,18 +2145,18 @@ function spliceManifestValue(text, options) {
|
|
|
2118
2145
|
const r = rangeOf(node);
|
|
2119
2146
|
return r ? lc.linePos(r[0]).line : void 0;
|
|
2120
2147
|
};
|
|
2121
|
-
const same =
|
|
2148
|
+
const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2122
2149
|
const root = doc.contents;
|
|
2123
2150
|
let spelled = entry;
|
|
2124
2151
|
let edit;
|
|
2125
|
-
if (root === null || isScalar2(root) && root.value === null && emptySource(
|
|
2126
|
-
edit = startManifest(
|
|
2152
|
+
if (root === null || isScalar2(root) && root.value === null && emptySource(text2, root)) {
|
|
2153
|
+
edit = startManifest(text2, entry, key, value, eol);
|
|
2127
2154
|
} else if (!isMap3(root)) {
|
|
2128
2155
|
throw new DocmetaError(
|
|
2129
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2156
|
+
`${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
|
|
2130
2157
|
);
|
|
2131
2158
|
} else {
|
|
2132
|
-
const style = detectStyle(
|
|
2159
|
+
const style = detectStyle(text2, root, eol);
|
|
2133
2160
|
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2134
2161
|
const [first, second] = matches;
|
|
2135
2162
|
if (first && second) {
|
|
@@ -2143,7 +2170,7 @@ function spliceManifestValue(text, options) {
|
|
|
2143
2170
|
`${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
|
|
2144
2171
|
);
|
|
2145
2172
|
}
|
|
2146
|
-
edit = appendEntry(
|
|
2173
|
+
edit = appendEntry(text2, root, entry, key, value, style);
|
|
2147
2174
|
} else {
|
|
2148
2175
|
spelled = keyString(first.key);
|
|
2149
2176
|
const where = at2(keyLine(first.key));
|
|
@@ -2166,18 +2193,18 @@ function spliceManifestValue(text, options) {
|
|
|
2166
2193
|
);
|
|
2167
2194
|
let made;
|
|
2168
2195
|
if (v.flow === true) {
|
|
2169
|
-
if (hit) made = replaceFlowValue(
|
|
2170
|
-
else if (v.items.length === 0) made = fillEmptyEntry(
|
|
2196
|
+
if (hit) made = replaceFlowValue(text2, hit, value);
|
|
2197
|
+
else if (v.items.length === 0) made = fillEmptyEntry(text2, first, key, value, style);
|
|
2171
2198
|
else throw flowRefusal();
|
|
2172
2199
|
if (!made) throw hit ? flowRefusal() : formRefusal();
|
|
2173
2200
|
} else {
|
|
2174
|
-
made = hit ? replaceBlockValue(
|
|
2201
|
+
made = hit ? replaceBlockValue(text2, hit, value, style) : insertKey(text2, v, key, value, style);
|
|
2175
2202
|
if (!made) throw formRefusal();
|
|
2176
2203
|
}
|
|
2177
2204
|
edit = made;
|
|
2178
2205
|
}
|
|
2179
2206
|
}
|
|
2180
|
-
const out =
|
|
2207
|
+
const out = text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
|
|
2181
2208
|
const line = readBack(
|
|
2182
2209
|
doc,
|
|
2183
2210
|
out,
|
|
@@ -2190,13 +2217,13 @@ function spliceManifestValue(text, options) {
|
|
|
2190
2217
|
);
|
|
2191
2218
|
return { text: out, line };
|
|
2192
2219
|
}
|
|
2193
|
-
function readManifestValue(
|
|
2194
|
-
const
|
|
2195
|
-
const doc = parseDocument3(
|
|
2220
|
+
function readManifestValue(text2, options) {
|
|
2221
|
+
const join5 = options.join ?? PATH_JOIN;
|
|
2222
|
+
const doc = parseDocument3(text2, { uniqueKeys: false });
|
|
2196
2223
|
if (doc.errors.length > 0) return void 0;
|
|
2197
2224
|
const root = doc.contents;
|
|
2198
2225
|
if (!isMap3(root)) return void 0;
|
|
2199
|
-
const same =
|
|
2226
|
+
const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2200
2227
|
const pair = root.items.find((p) => same(keyString(p.key), options.entry));
|
|
2201
2228
|
const held = pair?.value;
|
|
2202
2229
|
if (!isMap3(held)) return void 0;
|
|
@@ -2204,17 +2231,17 @@ function readManifestValue(text, options) {
|
|
|
2204
2231
|
const node = kv?.value;
|
|
2205
2232
|
return isNode2(node) ? node.toJSON() : void 0;
|
|
2206
2233
|
}
|
|
2207
|
-
function removeManifestKey(
|
|
2234
|
+
function removeManifestKey(text2, options) {
|
|
2208
2235
|
const { entry, key, file } = options;
|
|
2209
|
-
const
|
|
2236
|
+
const join5 = options.join ?? PATH_JOIN;
|
|
2210
2237
|
const at2 = (line) => {
|
|
2211
2238
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2212
2239
|
if (line === void 0) return name;
|
|
2213
2240
|
return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
|
|
2214
2241
|
};
|
|
2215
|
-
const eol = detectEol(
|
|
2242
|
+
const eol = detectEol(text2);
|
|
2216
2243
|
const lc = new LineCounter2();
|
|
2217
|
-
const doc = parseDocument3(
|
|
2244
|
+
const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
|
|
2218
2245
|
const problem = doc.errors[0];
|
|
2219
2246
|
if (problem) {
|
|
2220
2247
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2224,13 +2251,13 @@ function removeManifestKey(text, options) {
|
|
|
2224
2251
|
return r ? lc.linePos(r[0]).line : void 0;
|
|
2225
2252
|
};
|
|
2226
2253
|
const root = doc.contents;
|
|
2227
|
-
if (root === null || isScalar2(root) && root.value === null) return { text };
|
|
2254
|
+
if (root === null || isScalar2(root) && root.value === null) return { text: text2 };
|
|
2228
2255
|
if (!isMap3(root)) {
|
|
2229
2256
|
throw new DocmetaError(
|
|
2230
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2257
|
+
`${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
|
|
2231
2258
|
);
|
|
2232
2259
|
}
|
|
2233
|
-
const same =
|
|
2260
|
+
const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2234
2261
|
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2235
2262
|
const [first, second] = matches;
|
|
2236
2263
|
if (first && second) {
|
|
@@ -2238,7 +2265,7 @@ function removeManifestKey(text, options) {
|
|
|
2238
2265
|
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
2239
2266
|
);
|
|
2240
2267
|
}
|
|
2241
|
-
if (first === void 0) return { text };
|
|
2268
|
+
if (first === void 0) return { text: text2 };
|
|
2242
2269
|
const spelled = keyString(first.key);
|
|
2243
2270
|
const where = at2(keyLine(first.key));
|
|
2244
2271
|
const v = first.value;
|
|
@@ -2252,7 +2279,7 @@ function removeManifestKey(text, options) {
|
|
|
2252
2279
|
`${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
|
|
2253
2280
|
);
|
|
2254
2281
|
}
|
|
2255
|
-
if (hit === void 0) return { text };
|
|
2282
|
+
if (hit === void 0) return { text: text2 };
|
|
2256
2283
|
const whole = v.items.length === 1;
|
|
2257
2284
|
if (!whole && v.flow === true) {
|
|
2258
2285
|
throw new DocmetaError(
|
|
@@ -2260,18 +2287,18 @@ function removeManifestKey(text, options) {
|
|
|
2260
2287
|
);
|
|
2261
2288
|
}
|
|
2262
2289
|
const from = rangeOf(whole ? first.key : hit.key);
|
|
2263
|
-
const to = whole ? contentEnd(
|
|
2290
|
+
const to = whole ? contentEnd(text2, v) : contentEnd(text2, hit.value) ?? contentEnd(text2, hit.key);
|
|
2264
2291
|
if (!from || to === void 0) {
|
|
2265
2292
|
throw new DocmetaError(
|
|
2266
2293
|
`${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
|
|
2267
2294
|
);
|
|
2268
2295
|
}
|
|
2269
|
-
let { start, end } = keyLines(
|
|
2296
|
+
let { start, end } = keyLines(text2, from[0], to, eol);
|
|
2270
2297
|
if (whole) {
|
|
2271
|
-
if (
|
|
2272
|
-
else if (start === 0 &&
|
|
2298
|
+
if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
|
|
2299
|
+
else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
|
|
2273
2300
|
}
|
|
2274
|
-
const out =
|
|
2301
|
+
const out = text2.slice(0, start) + text2.slice(end);
|
|
2275
2302
|
const after = parseDocument3(out, { uniqueKeys: false });
|
|
2276
2303
|
const fail = (why) => new DocmetaError(
|
|
2277
2304
|
`${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
|
|
@@ -2290,16 +2317,16 @@ function removeManifestKey(text, options) {
|
|
|
2290
2317
|
if (!deepEqual(now ?? null, expected)) throw fail("Another value in the manifest would have changed.");
|
|
2291
2318
|
return { text: out };
|
|
2292
2319
|
}
|
|
2293
|
-
function renameManifestEntry(
|
|
2320
|
+
function renameManifestEntry(text2, options) {
|
|
2294
2321
|
const { entry, to, file } = options;
|
|
2295
|
-
const
|
|
2322
|
+
const join5 = options.join ?? PATH_JOIN;
|
|
2296
2323
|
const at2 = (line) => {
|
|
2297
2324
|
const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
2298
2325
|
if (line === void 0) return name;
|
|
2299
2326
|
return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
|
|
2300
2327
|
};
|
|
2301
2328
|
const lc = new LineCounter2();
|
|
2302
|
-
const doc = parseDocument3(
|
|
2329
|
+
const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
|
|
2303
2330
|
const problem = doc.errors[0];
|
|
2304
2331
|
if (problem) {
|
|
2305
2332
|
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
@@ -2309,20 +2336,20 @@ function renameManifestEntry(text, options) {
|
|
|
2309
2336
|
return r2 ? lc.linePos(r2[0]).line : void 0;
|
|
2310
2337
|
};
|
|
2311
2338
|
const root = doc.contents;
|
|
2312
|
-
if (root === null || isScalar2(root) && root.value === null) return { text };
|
|
2339
|
+
if (root === null || isScalar2(root) && root.value === null) return { text: text2 };
|
|
2313
2340
|
if (!isMap3(root)) {
|
|
2314
2341
|
throw new DocmetaError(
|
|
2315
|
-
`${at2()}: the manifest must be a mapping from document ${
|
|
2342
|
+
`${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
|
|
2316
2343
|
);
|
|
2317
2344
|
}
|
|
2318
|
-
const same =
|
|
2345
|
+
const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
2319
2346
|
const [first, second] = root.items.filter((p) => same(keyString(p.key), entry));
|
|
2320
2347
|
if (first && second) {
|
|
2321
2348
|
throw new DocmetaError(
|
|
2322
2349
|
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
2323
2350
|
);
|
|
2324
2351
|
}
|
|
2325
|
-
if (first === void 0) return { text };
|
|
2352
|
+
if (first === void 0) return { text: text2 };
|
|
2326
2353
|
const taken = root.items.find((p) => same(keyString(p.key), to));
|
|
2327
2354
|
if (taken !== void 0) {
|
|
2328
2355
|
throw new DocmetaError(
|
|
@@ -2331,8 +2358,8 @@ function renameManifestEntry(text, options) {
|
|
|
2331
2358
|
}
|
|
2332
2359
|
const spelled = keyString(first.key);
|
|
2333
2360
|
const r = rangeOf(first.key);
|
|
2334
|
-
const end = contentEnd(
|
|
2335
|
-
if (!r || end === void 0 || colonEnd(
|
|
2361
|
+
const end = contentEnd(text2, first.key);
|
|
2362
|
+
if (!r || end === void 0 || colonEnd(text2, first) === void 0) {
|
|
2336
2363
|
throw new DocmetaError(
|
|
2337
2364
|
`${at2(keyLine(first.key))}: "${spelled}" is written in a form this writer does not edit. Write it as "${spelled}: \u2026".`
|
|
2338
2365
|
);
|
|
@@ -2340,7 +2367,7 @@ function renameManifestEntry(text, options) {
|
|
|
2340
2367
|
const quoted = isScalar2(first.key) && (first.key.type === "QUOTE_DOUBLE" || first.key.type === "QUOTE_SINGLE");
|
|
2341
2368
|
const plain = stringify2(to, { lineWidth: 0 }).replace(/\n$/, "");
|
|
2342
2369
|
const insert = quoted || plain.includes("\n") ? JSON.stringify(to) : plain;
|
|
2343
|
-
const out =
|
|
2370
|
+
const out = text2.slice(0, r[0]) + insert + text2.slice(end);
|
|
2344
2371
|
const after = parseDocument3(out, { uniqueKeys: false });
|
|
2345
2372
|
const fail = (why) => new DocmetaError(
|
|
2346
2373
|
`${at2()}: renaming "${spelled}" to "${to}" did not read back as renamed, so nothing was written. ${why}`
|
|
@@ -2355,25 +2382,25 @@ function renameManifestEntry(text, options) {
|
|
|
2355
2382
|
if (!deepEqual(now, expected)) throw fail("Another value in the manifest would have changed.");
|
|
2356
2383
|
return { text: out };
|
|
2357
2384
|
}
|
|
2358
|
-
function startManifest(
|
|
2385
|
+
function startManifest(text2, entry, key, value, eol) {
|
|
2359
2386
|
const style = { step: 2, indentSeq: true, eol };
|
|
2360
2387
|
const block = blockLines({ [entry]: { [key]: value } }, 0, style);
|
|
2361
2388
|
let insert;
|
|
2362
|
-
if (
|
|
2389
|
+
if (text2 === "" || text2.endsWith("\n")) insert = block + eol;
|
|
2363
2390
|
else insert = eol + block;
|
|
2364
|
-
return { start:
|
|
2391
|
+
return { start: text2.length, end: text2.length, insert };
|
|
2365
2392
|
}
|
|
2366
|
-
function appendEntry(
|
|
2393
|
+
function appendEntry(text2, root, entry, key, value, style) {
|
|
2367
2394
|
const body = { [entry]: { [key]: value } };
|
|
2368
2395
|
if (root.flow === true) {
|
|
2369
2396
|
const r = rangeOf(root);
|
|
2370
|
-
const start = r ? r[0] :
|
|
2371
|
-
const end = contentEnd(
|
|
2372
|
-
return { start, end, insert: blockLines(body, colOf(
|
|
2397
|
+
const start = r ? r[0] : text2.length;
|
|
2398
|
+
const end = contentEnd(text2, root) ?? start;
|
|
2399
|
+
return { start, end, insert: blockLines(body, colOf(text2, start), style) };
|
|
2373
2400
|
}
|
|
2374
2401
|
const firstKey = rangeOf(root.items[0]?.key);
|
|
2375
|
-
const col = firstKey ? colOf(
|
|
2376
|
-
const pos = lineEnd(
|
|
2402
|
+
const col = firstKey ? colOf(text2, firstKey[0]) : 0;
|
|
2403
|
+
const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
|
|
2377
2404
|
const last = root.items.at(-1);
|
|
2378
2405
|
const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
|
|
2379
2406
|
return {
|
|
@@ -2382,12 +2409,12 @@ function appendEntry(text, root, entry, key, value, style) {
|
|
|
2382
2409
|
insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
|
|
2383
2410
|
};
|
|
2384
2411
|
}
|
|
2385
|
-
function fillEmptyEntry(
|
|
2386
|
-
const start = colonEnd(
|
|
2412
|
+
function fillEmptyEntry(text2, entryPair, key, value, style) {
|
|
2413
|
+
const start = colonEnd(text2, entryPair);
|
|
2387
2414
|
const keyRange = rangeOf(entryPair.key);
|
|
2388
|
-
const end = contentEnd(
|
|
2415
|
+
const end = contentEnd(text2, entryPair.value);
|
|
2389
2416
|
if (start === void 0 || !keyRange || end === void 0) return void 0;
|
|
2390
|
-
const col = colOf(
|
|
2417
|
+
const col = colOf(text2, keyRange[0]) + style.step;
|
|
2391
2418
|
return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
|
|
2392
2419
|
}
|
|
2393
2420
|
function readBack(before, out, spelled, key, value, fail) {
|
|
@@ -2428,17 +2455,17 @@ function isRecord3(x) {
|
|
|
2428
2455
|
function relocateFailed(result) {
|
|
2429
2456
|
return result.summary.stayed > 0;
|
|
2430
2457
|
}
|
|
2431
|
-
function relocationContext(
|
|
2458
|
+
function relocationContext(run2, extras) {
|
|
2432
2459
|
return {
|
|
2433
2460
|
cwd: extras.cwd,
|
|
2434
|
-
base:
|
|
2461
|
+
base: run2.base,
|
|
2435
2462
|
noConfig: extras.noConfig ?? false,
|
|
2436
|
-
config:
|
|
2437
|
-
...
|
|
2438
|
-
...
|
|
2439
|
-
...
|
|
2440
|
-
collections:
|
|
2441
|
-
declaredCollections:
|
|
2463
|
+
config: run2.config,
|
|
2464
|
+
...run2.configDir !== void 0 ? { configDir: run2.configDir } : {},
|
|
2465
|
+
...run2.configPath !== void 0 ? { configPath: run2.configPath } : {},
|
|
2466
|
+
...run2.configFile !== void 0 ? { configFile: run2.configFile } : {},
|
|
2467
|
+
collections: run2.collections,
|
|
2468
|
+
declaredCollections: run2.declaredCollections,
|
|
2442
2469
|
targets: extras.targets.filter((t) => t !== STDIN_TOKEN),
|
|
2443
2470
|
...extras.cliSchemas !== void 0 ? { cliSchemas: extras.cliSchemas } : {},
|
|
2444
2471
|
validator: extras.validator,
|
|
@@ -2677,17 +2704,17 @@ function bare(path) {
|
|
|
2677
2704
|
}
|
|
2678
2705
|
async function collectionPath(target, cwd, configDir) {
|
|
2679
2706
|
if (target === STDIN_TOKEN) return void 0;
|
|
2680
|
-
const
|
|
2681
|
-
const scanned = picomatch.scan(
|
|
2707
|
+
const posix5 = target.replace(/\\/g, "/");
|
|
2708
|
+
const scanned = picomatch.scan(posix5);
|
|
2682
2709
|
if (scanned.isGlob) {
|
|
2683
2710
|
const rel2 = toPosix(relative4(configDir, resolve3(cwd, scanned.base === "" ? "." : scanned.base)));
|
|
2684
2711
|
if (isAbsolute3(rel2) || rel2 === ".." || rel2.startsWith("../")) return void 0;
|
|
2685
2712
|
return rel2 === "" ? scanned.glob : `${rel2}/${scanned.glob}`;
|
|
2686
2713
|
}
|
|
2687
|
-
const abs = resolve3(cwd,
|
|
2714
|
+
const abs = resolve3(cwd, posix5);
|
|
2688
2715
|
const rel = toPosix(relative4(configDir, abs));
|
|
2689
2716
|
if (isAbsolute3(rel) || rel.startsWith("..")) return void 0;
|
|
2690
|
-
let directory =
|
|
2717
|
+
let directory = posix5.endsWith("/");
|
|
2691
2718
|
if (!directory) {
|
|
2692
2719
|
try {
|
|
2693
2720
|
directory = (await stat2(abs)).isDirectory();
|
|
@@ -3062,9 +3089,9 @@ async function planRelocation(ctx, request) {
|
|
|
3062
3089
|
}
|
|
3063
3090
|
const orderedPages = () => {
|
|
3064
3091
|
const all = [...pages.values()];
|
|
3065
|
-
const
|
|
3092
|
+
const run2 = all.filter((p) => !p.beyond).sort((a, b) => compare(a.label, b.label));
|
|
3066
3093
|
const beyond = all.filter((p) => p.beyond).sort((a, b) => compare(a.label, b.label));
|
|
3067
|
-
return [...
|
|
3094
|
+
return [...run2, ...beyond];
|
|
3068
3095
|
};
|
|
3069
3096
|
let dirty = new Set(pages.values());
|
|
3070
3097
|
while (dirty.size > 0) {
|
|
@@ -3126,20 +3153,20 @@ async function planRelocation(ctx, request) {
|
|
|
3126
3153
|
for (const [path, { file, outs, ins }] of files) {
|
|
3127
3154
|
if (!m.created && outs.length === 0 && ins.length === 0) continue;
|
|
3128
3155
|
const disk = m.created ? null : await readManifestText(path, file, m.perPage);
|
|
3129
|
-
let
|
|
3130
|
-
const before =
|
|
3156
|
+
let text2 = disk ?? "";
|
|
3157
|
+
const before = text2;
|
|
3131
3158
|
const where = { join: m.join, file };
|
|
3132
3159
|
for (const o of outs) {
|
|
3133
|
-
if (!o.drop)
|
|
3160
|
+
if (!o.drop) text2 = spliceManifestValue(text2, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
|
|
3134
3161
|
}
|
|
3135
|
-
for (const i of ins)
|
|
3136
|
-
const found = keyLines2(
|
|
3162
|
+
for (const i of ins) text2 = removeManifestKey(text2, { ...where, entry: i.entry, key: i.key }).text;
|
|
3163
|
+
const found = keyLines2(text2, m.join);
|
|
3137
3164
|
for (const o of outs) {
|
|
3138
3165
|
const line = found.get(entryId(o.entry, m.join))?.get(o.key);
|
|
3139
3166
|
if (line !== void 0) lines.set(lineId(o.page, file, o.key), line);
|
|
3140
3167
|
}
|
|
3141
|
-
if (m.created ||
|
|
3142
|
-
writes.push({ path, text, kind: "manifest", before: disk });
|
|
3168
|
+
if (m.created || text2 !== before) {
|
|
3169
|
+
writes.push({ path, text: text2, kind: "manifest", before: disk });
|
|
3143
3170
|
}
|
|
3144
3171
|
}
|
|
3145
3172
|
}
|
|
@@ -3153,9 +3180,9 @@ async function planRelocation(ctx, request) {
|
|
|
3153
3180
|
`${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"}.`
|
|
3154
3181
|
);
|
|
3155
3182
|
}
|
|
3156
|
-
const
|
|
3157
|
-
verifyConfig(target.text,
|
|
3158
|
-
writes.push({ path: target.path, text, kind: "config", before: target.disk });
|
|
3183
|
+
const text2 = editConfig(target.text, model, configSource(target.path, ctx.cwd));
|
|
3184
|
+
verifyConfig(target.text, text2, finalCollections, configSource(target.path, ctx.cwd));
|
|
3185
|
+
writes.push({ path: target.path, text: text2, kind: "config", before: target.disk });
|
|
3159
3186
|
configCreated = target.text === null;
|
|
3160
3187
|
}
|
|
3161
3188
|
for (const page of orderedPages()) {
|
|
@@ -3270,12 +3297,12 @@ async function readManifestText(path, file, perPage) {
|
|
|
3270
3297
|
throw new DocmetaError(`Manifest ${file} could not be read: ${errorMessage(err)}`);
|
|
3271
3298
|
}
|
|
3272
3299
|
}
|
|
3273
|
-
var entryId = (entry,
|
|
3300
|
+
var entryId = (entry, join5) => join5 === PATH_JOIN ? toPosix(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
|
|
3274
3301
|
var lineId = (page, manifest, key) => `${page}\0${manifest}\0${key}`;
|
|
3275
|
-
function keyLines2(
|
|
3302
|
+
function keyLines2(text2, join5) {
|
|
3276
3303
|
const out = /* @__PURE__ */ new Map();
|
|
3277
3304
|
const lc = new LineCounter3();
|
|
3278
|
-
const doc = parseDocument4(
|
|
3305
|
+
const doc = parseDocument4(text2, { lineCounter: lc, uniqueKeys: false });
|
|
3279
3306
|
const root = doc.contents;
|
|
3280
3307
|
if (!isMap4(root)) return out;
|
|
3281
3308
|
for (const pair of root.items) {
|
|
@@ -3287,15 +3314,15 @@ function keyLines2(text, join4) {
|
|
|
3287
3314
|
const range = isNode3(kv.key) ? kv.key.range : void 0;
|
|
3288
3315
|
if (range) keys.set(name, lc.linePos(range[0]).line);
|
|
3289
3316
|
}
|
|
3290
|
-
out.set(entryId(spelled,
|
|
3317
|
+
out.set(entryId(spelled, join5), keys);
|
|
3291
3318
|
}
|
|
3292
3319
|
return out;
|
|
3293
3320
|
}
|
|
3294
3321
|
async function configTarget(ctx, configDir) {
|
|
3295
3322
|
if (ctx.configPath !== void 0) {
|
|
3296
3323
|
try {
|
|
3297
|
-
const
|
|
3298
|
-
return { path: ctx.configPath, text, disk:
|
|
3324
|
+
const text2 = await readFile4(ctx.configPath, "utf8");
|
|
3325
|
+
return { path: ctx.configPath, text: text2, disk: text2 };
|
|
3299
3326
|
} catch (err) {
|
|
3300
3327
|
if (err.code !== "ENOENT") {
|
|
3301
3328
|
throw new DocmetaError(
|
|
@@ -3308,15 +3335,15 @@ async function configTarget(ctx, configDir) {
|
|
|
3308
3335
|
for (const name of FAMILY_CONFIG_NAMES) {
|
|
3309
3336
|
const path = join3(configDir, name);
|
|
3310
3337
|
try {
|
|
3311
|
-
const
|
|
3312
|
-
return { path, text, disk:
|
|
3338
|
+
const text2 = await readFile4(path, "utf8");
|
|
3339
|
+
return { path, text: text2, disk: text2 };
|
|
3313
3340
|
} catch {
|
|
3314
3341
|
}
|
|
3315
3342
|
}
|
|
3316
3343
|
return { path: join3(configDir, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"), text: null, disk: null };
|
|
3317
3344
|
}
|
|
3318
|
-
function editConfig(
|
|
3319
|
-
const doc = parseDocument4(
|
|
3345
|
+
function editConfig(text2, model, source) {
|
|
3346
|
+
const doc = parseDocument4(text2 ?? "");
|
|
3320
3347
|
const [problem] = doc.errors;
|
|
3321
3348
|
if (problem !== void 0) throw new DocmetaError(`${source}: invalid YAML: ${problem.message}`);
|
|
3322
3349
|
if (doc.contents !== null && !isMap4(doc.contents)) {
|
|
@@ -3488,7 +3515,7 @@ function buildResult(input) {
|
|
|
3488
3515
|
};
|
|
3489
3516
|
}
|
|
3490
3517
|
async function applyRelocation(ctx, plan, io = {}) {
|
|
3491
|
-
const write = io.write ?? ((path,
|
|
3518
|
+
const write = io.write ?? ((path, text2) => writeFileAtomic(path, text2, { createParents: true }));
|
|
3492
3519
|
const remove = io.remove ?? ((path) => unlink(path));
|
|
3493
3520
|
const label = (w) => w.kind === "config" ? configSource(w.path, ctx.cwd) : reported2(w.path, ctx.base);
|
|
3494
3521
|
for (const w of plan.writes) {
|
|
@@ -3660,6 +3687,12 @@ function relocationOffers(plan) {
|
|
|
3660
3687
|
return plan.offers;
|
|
3661
3688
|
}
|
|
3662
3689
|
|
|
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
|
+
|
|
3663
3696
|
// src/shared/pin.ts
|
|
3664
3697
|
var LineRangeError = class extends ToolError {
|
|
3665
3698
|
constructor(message) {
|
|
@@ -3667,12 +3700,12 @@ var LineRangeError = class extends ToolError {
|
|
|
3667
3700
|
this.name = "LineRangeError";
|
|
3668
3701
|
}
|
|
3669
3702
|
};
|
|
3670
|
-
function normalizeText(
|
|
3671
|
-
const body =
|
|
3703
|
+
function normalizeText(text2) {
|
|
3704
|
+
const body = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
3672
3705
|
return body.replace(/\r\n/g, "\n");
|
|
3673
3706
|
}
|
|
3674
|
-
function splitLines(
|
|
3675
|
-
const parts = normalizeText(
|
|
3707
|
+
function splitLines(text2) {
|
|
3708
|
+
const parts = normalizeText(text2).split("\n");
|
|
3676
3709
|
if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
|
|
3677
3710
|
return parts;
|
|
3678
3711
|
}
|
|
@@ -3689,8 +3722,8 @@ function sliceLines(lines, range, label) {
|
|
|
3689
3722
|
}
|
|
3690
3723
|
return lines.slice(start - 1, end).join("\n");
|
|
3691
3724
|
}
|
|
3692
|
-
function hashRange(
|
|
3693
|
-
return hashLines(sliceLines(splitLines(
|
|
3725
|
+
function hashRange(text2, range, key) {
|
|
3726
|
+
return hashLines(sliceLines(splitLines(text2), range), key);
|
|
3694
3727
|
}
|
|
3695
3728
|
function hashLines(joined, key) {
|
|
3696
3729
|
return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
|
|
@@ -3737,14 +3770,14 @@ function findWindows(lines, length, pin, key, opts) {
|
|
|
3737
3770
|
for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
|
|
3738
3771
|
let spent = 0;
|
|
3739
3772
|
for (const start of order) {
|
|
3740
|
-
const
|
|
3741
|
-
const bytes = Buffer.byteLength(
|
|
3773
|
+
const text2 = joined(start);
|
|
3774
|
+
const bytes = Buffer.byteLength(text2, "utf8");
|
|
3742
3775
|
if (spent + bytes > budget) {
|
|
3743
3776
|
if (counter !== void 0) counter.left = budget - spent;
|
|
3744
3777
|
return { starts, truncated: true };
|
|
3745
3778
|
}
|
|
3746
3779
|
spent += bytes;
|
|
3747
|
-
if (hashLines(
|
|
3780
|
+
if (hashLines(text2, key) === pin) starts.push(start);
|
|
3748
3781
|
}
|
|
3749
3782
|
if (counter !== void 0) counter.left = budget - spent;
|
|
3750
3783
|
return { starts, truncated: false };
|
|
@@ -3775,9 +3808,9 @@ function toBodyLines(lines, bodyLine) {
|
|
|
3775
3808
|
return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
|
|
3776
3809
|
}
|
|
3777
3810
|
function splitPageArgument(arg) {
|
|
3778
|
-
const
|
|
3779
|
-
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(
|
|
3780
|
-
const head = m === null ?
|
|
3811
|
+
const text2 = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
|
|
3812
|
+
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text2);
|
|
3813
|
+
const head = m === null ? text2 : text2.slice(0, m.index);
|
|
3781
3814
|
if (m === null || head === "") return { page: arg };
|
|
3782
3815
|
const start = Number(m[1]);
|
|
3783
3816
|
const end = m[2] === void 0 ? start : Number(m[2]);
|
|
@@ -3788,7 +3821,6 @@ function splitPageArgument(arg) {
|
|
|
3788
3821
|
}
|
|
3789
3822
|
|
|
3790
3823
|
// src/meta/core/derive/provenance.ts
|
|
3791
|
-
import picomatch2 from "picomatch";
|
|
3792
3824
|
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
3793
3825
|
var DEFAULT_MACHINES = ["*[bot]"];
|
|
3794
3826
|
var PROVENANCE_POINTER = "/provenance";
|
|
@@ -3843,8 +3875,8 @@ function readProvenancePage(content, opts) {
|
|
|
3843
3875
|
}
|
|
3844
3876
|
var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
|
|
3845
3877
|
var ALL_ZERO = /^0+$/;
|
|
3846
|
-
function parseLinePorcelain(
|
|
3847
|
-
const rows =
|
|
3878
|
+
function parseLinePorcelain(text2) {
|
|
3879
|
+
const rows = text2.split("\n");
|
|
3848
3880
|
if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
|
|
3849
3881
|
const out = [];
|
|
3850
3882
|
let i = 0;
|
|
@@ -4267,7 +4299,2940 @@ function attributeRange(input) {
|
|
|
4267
4299
|
return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
|
|
4268
4300
|
}
|
|
4269
4301
|
|
|
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 out = await run2([...LOG_ARGS, rev, "--follow", "--", pathspec(rel)]);
|
|
5323
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5324
|
+
if (out.code !== 0) {
|
|
5325
|
+
if (await isUnbornHead(run2)) return found;
|
|
5326
|
+
return null;
|
|
5327
|
+
}
|
|
5328
|
+
const attributed = attribute(parseLog(text(out)), [rel]);
|
|
5329
|
+
found.set(rel, attributed.get(rel) ?? []);
|
|
5330
|
+
}
|
|
5331
|
+
return found;
|
|
5332
|
+
}
|
|
5333
|
+
async function bulkHistories(run2, rels, rev) {
|
|
5334
|
+
const out = await run2([...LOG_ARGS, rev]);
|
|
5335
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5336
|
+
if (out.code !== 0) {
|
|
5337
|
+
if (await isUnbornHead(run2)) return /* @__PURE__ */ new Map();
|
|
5338
|
+
return null;
|
|
5339
|
+
}
|
|
5340
|
+
return attribute(parseLog(text(out)), [...rels]);
|
|
5341
|
+
}
|
|
5342
|
+
async function followTakenBodies(run2, bucket, found, blobs, threshold) {
|
|
5343
|
+
for (; ; ) {
|
|
5344
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
5345
|
+
for (const entry of bucket) {
|
|
5346
|
+
const history = found.get(entry.rel) ?? [];
|
|
5347
|
+
const taken = takenMerge(entry.input.extracted.fenced, history, blobs);
|
|
5348
|
+
if (taken === void 0) continue;
|
|
5349
|
+
const group = byParent.get(taken.parent) ?? [];
|
|
5350
|
+
group.push({ rel: entry.rel, keep: history.slice(0, taken.index), path: taken.path });
|
|
5351
|
+
byParent.set(taken.parent, group);
|
|
5352
|
+
}
|
|
5353
|
+
if (byParent.size === 0) return true;
|
|
5354
|
+
const walked = /* @__PURE__ */ new Map();
|
|
5355
|
+
for (const [parent, group] of byParent) {
|
|
5356
|
+
const paths = [...new Set(group.map((g) => g.path))];
|
|
5357
|
+
const side = await histories(run2, paths, threshold, parent);
|
|
5358
|
+
if (side === null) return false;
|
|
5359
|
+
for (const g of group) {
|
|
5360
|
+
const tail = side.get(g.path) ?? [];
|
|
5361
|
+
walked.set(g.rel, tail);
|
|
5362
|
+
found.set(g.rel, [...g.keep, ...tail]);
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
await readOtherParentBlobs(run2, walked);
|
|
5366
|
+
const missing = new Set([...neededBlobs(walked)].filter((sha) => !blobs.has(sha)));
|
|
5367
|
+
for (const [sha, content] of await fetchBlobs(run2, missing)) blobs.set(sha, content);
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
function takenMerge(fenced, history, blobs) {
|
|
5371
|
+
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5372
|
+
const index = history.findIndex((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
|
|
5373
|
+
const newest = history[index];
|
|
5374
|
+
if (newest === void 0) return void 0;
|
|
5375
|
+
const body = blobBody(newest.newBlob);
|
|
5376
|
+
const k = newest.otherParentBlobs.findIndex((b) => b !== null && sameBody(body, blobBody(b)));
|
|
5377
|
+
const parent = newest.otherParents[k];
|
|
5378
|
+
if (k === -1 || parent === void 0) return void 0;
|
|
5379
|
+
return { index, parent, path: newest.pathAtCommit };
|
|
5380
|
+
}
|
|
5381
|
+
function pathspec(rel) {
|
|
5382
|
+
return /[*?[\]]/.test(rel) ? `:(literal)${rel}` : rel;
|
|
5383
|
+
}
|
|
5384
|
+
async function isUnbornHead(run2) {
|
|
5385
|
+
const out = await run2(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
5386
|
+
return !out.missing && out.code !== 0;
|
|
5387
|
+
}
|
|
5388
|
+
function parseLog(text2) {
|
|
5389
|
+
const records = [];
|
|
5390
|
+
for (const chunk of text2.split("")) {
|
|
5391
|
+
if (chunk === "") continue;
|
|
5392
|
+
const nl = chunk.indexOf("\n");
|
|
5393
|
+
const header = nl === -1 ? chunk : chunk.slice(0, nl);
|
|
5394
|
+
const fields = header.split("\0");
|
|
5395
|
+
const sha = fields[0];
|
|
5396
|
+
if (sha === void 0 || sha === "") continue;
|
|
5397
|
+
const raw = [];
|
|
5398
|
+
if (nl !== -1) {
|
|
5399
|
+
for (const line of chunk.slice(nl + 1).split("\n")) {
|
|
5400
|
+
const entry = parseRawLine(line);
|
|
5401
|
+
if (entry) raw.push(entry);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
records.push({
|
|
5405
|
+
sha,
|
|
5406
|
+
authorDate: fields[1] ?? "",
|
|
5407
|
+
authorName: fields[2] ?? "",
|
|
5408
|
+
authorEmail: fields[3] ?? "",
|
|
5409
|
+
coAuthors: splitTrailers(fields[4]),
|
|
5410
|
+
reviewedBy: splitTrailers(fields[5]),
|
|
5411
|
+
generatedBy: splitTrailers(fields[6]),
|
|
5412
|
+
parents: (fields[7] ?? "").trim().split(" ").filter((p) => p !== ""),
|
|
5413
|
+
raw
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
return records;
|
|
5417
|
+
}
|
|
5418
|
+
function splitTrailers(field) {
|
|
5419
|
+
if (field === void 0 || field === "") return [];
|
|
5420
|
+
return field.split("").map((s) => s.trim()).filter((s) => s !== "");
|
|
5421
|
+
}
|
|
5422
|
+
function parseRawLine(line) {
|
|
5423
|
+
if (!line.startsWith(":")) return null;
|
|
5424
|
+
const tab = line.indexOf(" ");
|
|
5425
|
+
if (tab === -1) return null;
|
|
5426
|
+
const meta = line.slice(1, tab).split(" ");
|
|
5427
|
+
const oldBlob = meta[2];
|
|
5428
|
+
const newBlob = meta[3];
|
|
5429
|
+
const status = meta[4];
|
|
5430
|
+
if (oldBlob === void 0 || newBlob === void 0 || status === void 0) {
|
|
5431
|
+
return null;
|
|
5432
|
+
}
|
|
5433
|
+
const paths = line.slice(tab + 1).split(" ").map(unquotePath);
|
|
5434
|
+
const first = paths[0];
|
|
5435
|
+
if (first === void 0) return null;
|
|
5436
|
+
const second = paths[1];
|
|
5437
|
+
const renamed = status.startsWith("R") || status.startsWith("C");
|
|
5438
|
+
return {
|
|
5439
|
+
status,
|
|
5440
|
+
oldBlob,
|
|
5441
|
+
newBlob,
|
|
5442
|
+
oldPath: first,
|
|
5443
|
+
newPath: renamed && second !== void 0 ? second : first
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
5446
|
+
function unquotePath(p) {
|
|
5447
|
+
if (p.length < 2 || !p.startsWith('"') || !p.endsWith('"')) return p;
|
|
5448
|
+
const inner = p.slice(1, -1);
|
|
5449
|
+
const bytes = [];
|
|
5450
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
5451
|
+
const ch = inner[i];
|
|
5452
|
+
if (ch !== "\\") {
|
|
5453
|
+
bytes.push(...Buffer.from(ch ?? "", "utf8"));
|
|
5454
|
+
continue;
|
|
5455
|
+
}
|
|
5456
|
+
const next = inner[i + 1];
|
|
5457
|
+
if (next === void 0) break;
|
|
5458
|
+
const octal = /^[0-7]{3}/.exec(inner.slice(i + 1));
|
|
5459
|
+
if (octal) {
|
|
5460
|
+
bytes.push(parseInt(octal[0], 8));
|
|
5461
|
+
i += 3;
|
|
5462
|
+
continue;
|
|
5463
|
+
}
|
|
5464
|
+
const simple = {
|
|
5465
|
+
a: 7,
|
|
5466
|
+
b: 8,
|
|
5467
|
+
t: 9,
|
|
5468
|
+
n: 10,
|
|
5469
|
+
v: 11,
|
|
5470
|
+
f: 12,
|
|
5471
|
+
r: 13,
|
|
5472
|
+
'"': 34,
|
|
5473
|
+
"\\": 92
|
|
5474
|
+
};
|
|
5475
|
+
const code = simple[next];
|
|
5476
|
+
if (code === void 0) bytes.push(...Buffer.from(next, "utf8"));
|
|
5477
|
+
else bytes.push(code);
|
|
5478
|
+
i += 1;
|
|
5479
|
+
}
|
|
5480
|
+
return Buffer.from(bytes).toString("utf8");
|
|
5481
|
+
}
|
|
5482
|
+
function attribute(records, wanted) {
|
|
5483
|
+
const histories2 = /* @__PURE__ */ new Map();
|
|
5484
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
5485
|
+
for (const rel of wanted) {
|
|
5486
|
+
histories2.set(rel, []);
|
|
5487
|
+
tracked.set(rel, rel);
|
|
5488
|
+
}
|
|
5489
|
+
for (const record of records) {
|
|
5490
|
+
if (record.raw.length === 0) continue;
|
|
5491
|
+
const byNewPath = /* @__PURE__ */ new Map();
|
|
5492
|
+
for (const entry of record.raw) byNewPath.set(entry.newPath, entry);
|
|
5493
|
+
for (const [rel, current] of tracked) {
|
|
5494
|
+
const entry = byNewPath.get(current);
|
|
5495
|
+
if (!entry) continue;
|
|
5496
|
+
histories2.get(rel)?.push({
|
|
5497
|
+
sha: record.sha,
|
|
5498
|
+
authorDate: record.authorDate,
|
|
5499
|
+
authorName: record.authorName,
|
|
5500
|
+
authorEmail: record.authorEmail,
|
|
5501
|
+
coAuthors: record.coAuthors,
|
|
5502
|
+
reviewedBy: record.reviewedBy,
|
|
5503
|
+
generatedBy: record.generatedBy,
|
|
5504
|
+
oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
|
|
5505
|
+
newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
|
|
5506
|
+
pathAtCommit: entry.newPath,
|
|
5507
|
+
otherParents: record.parents.slice(1),
|
|
5508
|
+
otherParentBlobs: []
|
|
5509
|
+
});
|
|
5510
|
+
if (entry.oldPath !== entry.newPath) tracked.set(rel, entry.oldPath);
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
return histories2;
|
|
5514
|
+
}
|
|
5515
|
+
function neededBlobs(histories2) {
|
|
5516
|
+
const shas = /* @__PURE__ */ new Set();
|
|
5517
|
+
for (const history of histories2.values()) {
|
|
5518
|
+
for (const h of history) {
|
|
5519
|
+
if (h.oldBlob !== null) shas.add(h.oldBlob);
|
|
5520
|
+
if (h.newBlob !== null) shas.add(h.newBlob);
|
|
5521
|
+
for (const b of h.otherParentBlobs) if (b !== null) shas.add(b);
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
return shas;
|
|
5525
|
+
}
|
|
5526
|
+
async function readOtherParentBlobs(run2, histories2) {
|
|
5527
|
+
const merges = [];
|
|
5528
|
+
const specs = [];
|
|
5529
|
+
for (const history of histories2.values()) {
|
|
5530
|
+
for (const h of history) {
|
|
5531
|
+
if (h.otherParents.length === 0) continue;
|
|
5532
|
+
merges.push(h);
|
|
5533
|
+
for (const parent of h.otherParents) specs.push(`${parent}:${h.pathAtCommit}`);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
if (specs.length === 0) return;
|
|
5537
|
+
const out = await run2(["cat-file", "--batch-check"], `${specs.join("\n")}
|
|
5538
|
+
`);
|
|
5539
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5540
|
+
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5541
|
+
const lines = text(out).split("\n");
|
|
5542
|
+
let i = 0;
|
|
5543
|
+
for (const h of merges) {
|
|
5544
|
+
h.otherParentBlobs = h.otherParents.map(() => blobOfCheckLine(lines[i++]));
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
function blobOfCheckLine(line) {
|
|
5548
|
+
const [sha, kind] = (line ?? "").split(" ");
|
|
5549
|
+
if (sha === void 0 || kind !== "blob" || !/^[0-9a-f]{40,64}$/.test(sha)) return null;
|
|
5550
|
+
return sha;
|
|
5551
|
+
}
|
|
5552
|
+
async function fetchBlobs(run2, shas) {
|
|
5553
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
5554
|
+
if (shas.size === 0) return blobs;
|
|
5555
|
+
const out = await run2(["cat-file", "--batch"], `${[...shas].join("\n")}
|
|
5556
|
+
`);
|
|
5557
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5558
|
+
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5559
|
+
const buf = out.raw;
|
|
5560
|
+
let cursor = 0;
|
|
5561
|
+
while (cursor < buf.length) {
|
|
5562
|
+
const nl = buf.indexOf(10, cursor);
|
|
5563
|
+
if (nl === -1) break;
|
|
5564
|
+
const header = buf.toString("utf8", cursor, nl).split(" ");
|
|
5565
|
+
cursor = nl + 1;
|
|
5566
|
+
const sha = header[0];
|
|
5567
|
+
const kind = header[1];
|
|
5568
|
+
const size = Number(header[2]);
|
|
5569
|
+
if (sha === void 0 || kind !== "blob" || !Number.isFinite(size)) {
|
|
5570
|
+
continue;
|
|
5571
|
+
}
|
|
5572
|
+
blobs.set(sha, buf.toString("utf8", cursor, cursor + size));
|
|
5573
|
+
cursor += size + 1;
|
|
5574
|
+
}
|
|
5575
|
+
return blobs;
|
|
5576
|
+
}
|
|
5577
|
+
var git = (value, evidence) => ({
|
|
5578
|
+
value,
|
|
5579
|
+
source: "git",
|
|
5580
|
+
evidence
|
|
5581
|
+
});
|
|
5582
|
+
function factCommits(input, history, blobs) {
|
|
5583
|
+
const fenced = input.extracted.fenced;
|
|
5584
|
+
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5585
|
+
const bodyChanging = [...history].reverse().filter((h) => {
|
|
5586
|
+
const body = blobBody(h.newBlob);
|
|
5587
|
+
if (sameBody(body, blobBody(h.oldBlob))) return false;
|
|
5588
|
+
return !h.otherParentBlobs.some((b) => b !== null && sameBody(body, blobBody(b)));
|
|
5589
|
+
});
|
|
5590
|
+
return {
|
|
5591
|
+
bodyChanging,
|
|
5592
|
+
oldest: history[history.length - 1],
|
|
5593
|
+
newestBody: bodyChanging[bodyChanging.length - 1]
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
async function stampsInManifests(run2, root, bucket, histories2, blobs, parse) {
|
|
5597
|
+
const reads = [];
|
|
5598
|
+
for (const entry of bucket) {
|
|
5599
|
+
const refs = entry.input.stampManifests;
|
|
5600
|
+
if (refs === void 0) continue;
|
|
5601
|
+
const { oldest, newestBody } = factCommits(entry.input, histories2.get(entry.rel) ?? [], blobs);
|
|
5602
|
+
for (const field of STAMPED_DATE_FIELDS) {
|
|
5603
|
+
const ref = refs[field];
|
|
5604
|
+
const at2 = field === "created" ? oldest : newestBody;
|
|
5605
|
+
if (ref === void 0 || at2 === void 0) continue;
|
|
5606
|
+
const rel = insideRoot(root, ref.absPath);
|
|
5607
|
+
if (rel === void 0) continue;
|
|
5608
|
+
const key = ref.join === "path" ? entryAt(ref.entry, entry.rel, at2.pathAtCommit) : ref.entry;
|
|
5609
|
+
const revs = field === "created" ? [at2.sha] : [at2.sha, `${at2.sha}^`];
|
|
5610
|
+
for (const rev of revs) {
|
|
5611
|
+
reads.push({ label: entry.input.label, field, rev, spec: `${rev}:${rel}`, key, join: ref.join });
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
const out = /* @__PURE__ */ new Map();
|
|
5616
|
+
if (reads.length === 0) return out;
|
|
5617
|
+
const fetched = await fetchSpecs(run2, [...new Set(reads.map((r) => r.spec))]);
|
|
5618
|
+
const values = /* @__PURE__ */ new Map();
|
|
5619
|
+
const keyOf = (label, field, rev) => `${label}\0${field}\0${rev}`;
|
|
5620
|
+
for (const r of reads) {
|
|
5621
|
+
values.set(keyOf(r.label, r.field, r.rev), manifestValue(fetched.get(r.spec), r.key, r.join, r.field, parse));
|
|
5622
|
+
}
|
|
5623
|
+
for (const label of new Set(reads.map((r) => r.label))) {
|
|
5624
|
+
out.set(label, (field, rev) => values.get(keyOf(label, field, rev)));
|
|
5625
|
+
}
|
|
5626
|
+
return out;
|
|
5627
|
+
}
|
|
5628
|
+
function judge(input, root, history, blobs, now, machines, manifestStamp2) {
|
|
5629
|
+
const fenced = input.extracted.fenced;
|
|
5630
|
+
const extractor = extractorFor(input);
|
|
5631
|
+
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
5632
|
+
const stampsOf = (sha) => {
|
|
5633
|
+
if (sha === null) return {};
|
|
5634
|
+
const content = blobs.get(sha);
|
|
5635
|
+
return content === void 0 ? {} : extractStamps(extractor, content, input.absPath);
|
|
5636
|
+
};
|
|
5637
|
+
const stampAt = (field, h, parent) => {
|
|
5638
|
+
const kept = manifestStamp2?.(field, parent ? `${h.sha}^` : h.sha);
|
|
5639
|
+
if (kept !== void 0 && kept !== null) return kept;
|
|
5640
|
+
return stampsOf(parent ? h.oldBlob : h.newBlob)[field];
|
|
5641
|
+
};
|
|
5642
|
+
const { bodyChanging, oldest, newestBody } = factCommits(input, history, blobs);
|
|
5643
|
+
const head = history[0];
|
|
5644
|
+
const headBlob = head === void 0 ? null : head.newBlob;
|
|
5645
|
+
const created = oldest === void 0 ? null : createdOf(oldest, stampAt("created", oldest, false));
|
|
5646
|
+
const workingChanged = !sameBody(
|
|
5647
|
+
bodyOf(input.content, fenced),
|
|
5648
|
+
headBlob === null ? null : blobBody(headBlob)
|
|
5649
|
+
);
|
|
5650
|
+
let lastUpdated;
|
|
5651
|
+
let lastBodyCommit;
|
|
5652
|
+
if (workingChanged) {
|
|
5653
|
+
lastBodyCommit = null;
|
|
5654
|
+
const working = input.extracted.data["last-updated"];
|
|
5655
|
+
const committed = stampsOf(headBlob)["last-updated"];
|
|
5656
|
+
lastUpdated = working !== void 0 && working !== null && !sameValue(working, committed) ? git(working, "stamped in working tree") : git(utcDate2(now()), "uncommitted body change");
|
|
5657
|
+
} else if (newestBody === void 0) {
|
|
5658
|
+
lastBodyCommit = null;
|
|
5659
|
+
lastUpdated = null;
|
|
5660
|
+
} else {
|
|
5661
|
+
lastBodyCommit = newestBody.sha;
|
|
5662
|
+
lastUpdated = lastUpdatedOf(
|
|
5663
|
+
newestBody,
|
|
5664
|
+
stampAt("last-updated", newestBody, false),
|
|
5665
|
+
stampAt("last-updated", newestBody, true)
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
|
|
5669
|
+
let reviewedBy = null;
|
|
5670
|
+
let lastReviewed = null;
|
|
5671
|
+
if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
|
|
5672
|
+
const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
|
|
5673
|
+
reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
|
|
5674
|
+
lastReviewed = git(datePart(newestBody.authorDate), evidence);
|
|
5675
|
+
}
|
|
5676
|
+
return {
|
|
5677
|
+
created,
|
|
5678
|
+
"last-updated": lastUpdated,
|
|
5679
|
+
authors,
|
|
5680
|
+
"reviewed-by": reviewedBy,
|
|
5681
|
+
"last-reviewed": lastReviewed,
|
|
5682
|
+
provenance: null,
|
|
5683
|
+
lastBodyCommit,
|
|
5684
|
+
root
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
function createdOf(oldest, stamp) {
|
|
5688
|
+
if (stamp !== void 0 && stamp !== null) {
|
|
5689
|
+
return git(stamp, `stamped in ${short2(oldest.sha)}`);
|
|
5690
|
+
}
|
|
5691
|
+
const date = datePart(oldest.authorDate);
|
|
5692
|
+
return git(date, `added in ${short2(oldest.sha)} (${date})`);
|
|
5693
|
+
}
|
|
5694
|
+
function lastUpdatedOf(commit, stamp, before) {
|
|
5695
|
+
if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before)) {
|
|
5696
|
+
return git(stamp, `stamped in ${short2(commit.sha)}`);
|
|
5697
|
+
}
|
|
5698
|
+
const date = datePart(commit.authorDate);
|
|
5699
|
+
return git(date, `body changed in ${short2(commit.sha)} (${date})`);
|
|
5700
|
+
}
|
|
5701
|
+
function authorsOf(bodyChanging, machines) {
|
|
5702
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5703
|
+
const names = [];
|
|
5704
|
+
const add = (name, email) => {
|
|
5705
|
+
if (name === "") return;
|
|
5706
|
+
const identity = email === null || email === "" ? name : `${name} <${email}>`;
|
|
5707
|
+
if (machineIdentity(identity, machines) !== void 0) return;
|
|
5708
|
+
const key = email === null || email === "" ? `name:${name.toLowerCase()}` : email.toLowerCase();
|
|
5709
|
+
if (seen.has(key)) return;
|
|
5710
|
+
seen.add(key);
|
|
5711
|
+
names.push(name);
|
|
5712
|
+
};
|
|
5713
|
+
for (const h of bodyChanging) {
|
|
5714
|
+
add(h.authorName, h.authorEmail);
|
|
5715
|
+
for (const co of h.coAuthors) add(nameOf(co), emailOf(co));
|
|
5716
|
+
}
|
|
5717
|
+
const n = bodyChanging.length;
|
|
5718
|
+
return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
|
|
5719
|
+
}
|
|
5720
|
+
function provenanceFenced(extracted) {
|
|
5721
|
+
return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
|
|
5722
|
+
}
|
|
5723
|
+
var OUTSIDE_RANGE = "outside-the-attributed-range";
|
|
5724
|
+
var ATTRIBUTED = ":attributed";
|
|
5725
|
+
function provenanceFor(entry, blame, commits, opts) {
|
|
5726
|
+
const { input } = entry;
|
|
5727
|
+
const fenced = provenanceFenced(input.extracted);
|
|
5728
|
+
const base = { content: input.content, commits, machines: opts.machines, fenced };
|
|
5729
|
+
let derivation;
|
|
5730
|
+
const attribution = input.attribution;
|
|
5731
|
+
if (attribution !== void 0) {
|
|
5732
|
+
const page = readProvenancePage(input.content, { fenced });
|
|
5733
|
+
const { bodyLine } = page;
|
|
5734
|
+
const ranges = attribution.targets.flatMap((target) => {
|
|
5735
|
+
const entry2 = attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy, page });
|
|
5736
|
+
const span = entry2 === void 0 ? void 0 : parseLines(entry2.lines);
|
|
5737
|
+
return span === void 0 ? [] : [toFileLines(span, bodyLine)];
|
|
5738
|
+
});
|
|
5739
|
+
const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
|
|
5740
|
+
const attributed = new Map(commits);
|
|
5741
|
+
const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
|
|
5742
|
+
attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
|
|
5743
|
+
const rekeyed = blame.map((line) => {
|
|
5744
|
+
if (inRange(line)) {
|
|
5745
|
+
if (line.uncommitted) return line;
|
|
5746
|
+
const sha = `${line.sha}${ATTRIBUTED}`;
|
|
5747
|
+
attributed.set(sha, { sha, trailers });
|
|
5748
|
+
return { ...line, sha };
|
|
5749
|
+
}
|
|
5750
|
+
return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
|
|
5751
|
+
});
|
|
5752
|
+
derivation = deriveProvenance({
|
|
5753
|
+
...base,
|
|
5754
|
+
commits: attributed,
|
|
5755
|
+
generatedBy: attribution.generatedBy,
|
|
5756
|
+
blame: rekeyed
|
|
5757
|
+
});
|
|
5758
|
+
} else {
|
|
5759
|
+
derivation = deriveProvenance({
|
|
5760
|
+
...base,
|
|
5761
|
+
blame,
|
|
5762
|
+
...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
|
|
5763
|
+
});
|
|
5764
|
+
}
|
|
5765
|
+
const entries = derivation.derived.map((d) => d.entry);
|
|
5766
|
+
return {
|
|
5767
|
+
value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
|
|
5768
|
+
derivation
|
|
5769
|
+
};
|
|
5770
|
+
}
|
|
5771
|
+
function provenanceEvidence(derivation) {
|
|
5772
|
+
const shas = [];
|
|
5773
|
+
let uncommitted = false;
|
|
5774
|
+
for (const range of derivation.derived) {
|
|
5775
|
+
for (let n = range.span.start; n <= range.span.end; n++) {
|
|
5776
|
+
const evidence = derivation.evidenceByLine.get(n);
|
|
5777
|
+
if (evidence?.machine === void 0) continue;
|
|
5778
|
+
if (evidence.rule === 1) uncommitted = true;
|
|
5779
|
+
else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
const first = shas[0];
|
|
5783
|
+
if (first === void 0) return "uncommitted";
|
|
5784
|
+
const parts = [`blame ${short2(first)}`];
|
|
5785
|
+
if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
|
|
5786
|
+
if (uncommitted) parts.push("uncommitted");
|
|
5787
|
+
return parts.join(", ");
|
|
5788
|
+
}
|
|
5789
|
+
async function blameOf(run2, rel, content) {
|
|
5790
|
+
const out = await run2([
|
|
5791
|
+
"-c",
|
|
5792
|
+
"core.quotePath=false",
|
|
5793
|
+
"blame",
|
|
5794
|
+
"--no-ignore-revs-file",
|
|
5795
|
+
"--line-porcelain",
|
|
5796
|
+
"--",
|
|
5797
|
+
rel
|
|
5798
|
+
]);
|
|
5799
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5800
|
+
if (out.code !== 0) {
|
|
5801
|
+
const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
|
|
5802
|
+
if (inHead.code !== 0) {
|
|
5803
|
+
return splitLines(content).map((line, i) => ({
|
|
5804
|
+
sha: ZERO_SHA,
|
|
5805
|
+
origLine: i + 1,
|
|
5806
|
+
finalLine: i + 1,
|
|
5807
|
+
author: "Not Committed Yet",
|
|
5808
|
+
authorMail: "not.committed.yet",
|
|
5809
|
+
filename: rel,
|
|
5810
|
+
boundary: false,
|
|
5811
|
+
uncommitted: true,
|
|
5812
|
+
content: line
|
|
5813
|
+
}));
|
|
5814
|
+
}
|
|
5815
|
+
throw new BlameUnreadable(rel, out.stderr.trim());
|
|
5816
|
+
}
|
|
5817
|
+
return parseLinePorcelain(text(out));
|
|
5818
|
+
}
|
|
5819
|
+
async function commitEvidence(run2, root, pages, blobs, parse) {
|
|
5820
|
+
const plans = pages.map(({ entry, blame, history }) => {
|
|
5821
|
+
const pathAt = /* @__PURE__ */ new Map();
|
|
5822
|
+
for (const line of blame) {
|
|
5823
|
+
if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
|
|
5824
|
+
}
|
|
5825
|
+
const trailers = /* @__PURE__ */ new Map();
|
|
5826
|
+
const pageBlob = /* @__PURE__ */ new Map();
|
|
5827
|
+
for (const h of history) {
|
|
5828
|
+
trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
|
|
5829
|
+
const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
|
|
5830
|
+
if (blob !== void 0) pageBlob.set(h.sha, blob);
|
|
5831
|
+
}
|
|
5832
|
+
const manifest = entry.input.provenanceManifest;
|
|
5833
|
+
const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
|
|
5834
|
+
return { entry, pathAt, trailers, pageBlob, manifest, manifestRel };
|
|
5835
|
+
});
|
|
5836
|
+
const unlogged = /* @__PURE__ */ new Set();
|
|
5837
|
+
const specs = /* @__PURE__ */ new Set();
|
|
5838
|
+
for (const { pathAt, trailers, pageBlob, manifestRel } of plans) {
|
|
5839
|
+
for (const [sha, path] of pathAt) {
|
|
5840
|
+
if (!trailers.has(sha)) unlogged.add(sha);
|
|
5841
|
+
if (!pageBlob.has(sha)) specs.add(`${sha}:${path}`);
|
|
5842
|
+
if (manifestRel !== void 0) specs.add(`${sha}:${manifestRel}`);
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
const logged = /* @__PURE__ */ new Map();
|
|
5846
|
+
if (unlogged.size > 0) {
|
|
5847
|
+
const out = await run2(["log", "--no-walk=unsorted", "--stdin", RECORD_FORMAT], `${[...unlogged].join("\n")}
|
|
5848
|
+
`);
|
|
5849
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5850
|
+
if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
|
|
5851
|
+
for (const record of parseLog(text(out))) {
|
|
5852
|
+
logged.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
const fetched = await fetchSpecs(run2, [...specs]);
|
|
5856
|
+
return plans.map(({ entry, pathAt, trailers, pageBlob, manifest, manifestRel }) => {
|
|
5857
|
+
const commits = /* @__PURE__ */ new Map();
|
|
5858
|
+
for (const [sha, path] of pathAt) {
|
|
5859
|
+
const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${path}`);
|
|
5860
|
+
commits.set(sha, {
|
|
5861
|
+
sha,
|
|
5862
|
+
trailers: trailers.get(sha) ?? logged.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
|
|
5863
|
+
...blob !== void 0 ? { blob } : {},
|
|
5864
|
+
...manifest !== void 0 ? {
|
|
5865
|
+
stamp: manifestRel === void 0 ? [] : manifestStamp(
|
|
5866
|
+
fetched.get(`${sha}:${manifestRel}`),
|
|
5867
|
+
manifest.join === "path" ? entryAt(manifest.entry, entry.rel, path) : manifest.entry,
|
|
5868
|
+
manifest.join,
|
|
5869
|
+
parse
|
|
5870
|
+
)
|
|
5871
|
+
} : {}
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
return commits;
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
function insideRoot(root, abs) {
|
|
5878
|
+
const rel = relative6(root, abs);
|
|
5879
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute4(rel)) return void 0;
|
|
5880
|
+
return rel.split(sep5).join("/");
|
|
5881
|
+
}
|
|
5882
|
+
function entryAt(entry, rel, relAtCommit) {
|
|
5883
|
+
if (relAtCommit === rel) return entry;
|
|
5884
|
+
const fromHere = posix4.relative(posix4.dirname(rel), relAtCommit);
|
|
5885
|
+
return posix4.join(posix4.dirname(entry), fromHere);
|
|
5886
|
+
}
|
|
5887
|
+
function manifestStamp(text2, key, join5, parse) {
|
|
5888
|
+
return provenanceEntries(manifestValue(text2, key, join5, PROVENANCE_FIELD, parse));
|
|
5889
|
+
}
|
|
5890
|
+
function yamlOnce() {
|
|
5891
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
5892
|
+
return (text2) => {
|
|
5893
|
+
if (parsed.has(text2)) return parsed.get(text2);
|
|
5894
|
+
let data;
|
|
5895
|
+
try {
|
|
5896
|
+
data = parseYaml3(text2);
|
|
5897
|
+
} catch {
|
|
5898
|
+
data = void 0;
|
|
5899
|
+
}
|
|
5900
|
+
parsed.set(text2, data);
|
|
5901
|
+
return data;
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5904
|
+
function manifestValue(text2, key, join5, field, parse) {
|
|
5905
|
+
if (text2 === void 0) return void 0;
|
|
5906
|
+
const data = parse(text2);
|
|
5907
|
+
if (!isRecord6(data)) return void 0;
|
|
5908
|
+
for (const [candidate, value] of Object.entries(data)) {
|
|
5909
|
+
const same = join5 === "path" ? posix4.normalize(candidate) === posix4.normalize(key) : candidate === key;
|
|
5910
|
+
if (same && isRecord6(value)) return value[field];
|
|
5911
|
+
}
|
|
5912
|
+
return void 0;
|
|
5913
|
+
}
|
|
5914
|
+
function isRecord6(x) {
|
|
5915
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
5916
|
+
}
|
|
5917
|
+
async function fetchSpecs(run2, specs) {
|
|
5918
|
+
const found = /* @__PURE__ */ new Map();
|
|
5919
|
+
if (specs.length === 0) return found;
|
|
5920
|
+
const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
|
|
5921
|
+
`);
|
|
5922
|
+
if (out.tooLarge && specs.length > 1) {
|
|
5923
|
+
const half = Math.ceil(specs.length / 2);
|
|
5924
|
+
const first = await fetchSpecs(run2, specs.slice(0, half));
|
|
5925
|
+
const rest = await fetchSpecs(run2, specs.slice(half));
|
|
5926
|
+
return new Map([...first, ...rest]);
|
|
5927
|
+
}
|
|
5928
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
5929
|
+
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
5930
|
+
for (const [spec, content] of parseSpecBatch(out.raw, specs)) found.set(spec, content);
|
|
5931
|
+
return found;
|
|
5932
|
+
}
|
|
5933
|
+
function parseSpecBatch(buf, specs) {
|
|
5934
|
+
const found = /* @__PURE__ */ new Map();
|
|
5935
|
+
let cursor = 0;
|
|
5936
|
+
for (const spec of specs) {
|
|
5937
|
+
const nl = buf.indexOf(10, cursor);
|
|
5938
|
+
if (nl === -1) throw new BlobsUnreadable(`the answer ends before ${spec}`);
|
|
5939
|
+
const header = buf.toString("utf8", cursor, nl);
|
|
5940
|
+
cursor = nl + 1;
|
|
5941
|
+
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
|
|
5942
|
+
const parts = header.split(" ");
|
|
5943
|
+
const size = Number(parts[2]);
|
|
5944
|
+
if (parts.length !== 3 || !Number.isInteger(size) || size < 0) {
|
|
5945
|
+
throw new BlobsUnreadable(`unreadable header for ${spec}: ${header}`);
|
|
5946
|
+
}
|
|
5947
|
+
if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
|
|
5948
|
+
cursor += size + 1;
|
|
5949
|
+
}
|
|
5950
|
+
return found;
|
|
5951
|
+
}
|
|
5952
|
+
function extractorFor(input) {
|
|
5953
|
+
return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
|
|
5954
|
+
}
|
|
5955
|
+
function extractStamps(extractor, content, filePath) {
|
|
5956
|
+
if (!extractor) return {};
|
|
5957
|
+
try {
|
|
5958
|
+
return extractor.extract(content, filePath).data;
|
|
5959
|
+
} catch {
|
|
5960
|
+
return {};
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
function sameBody(a, b) {
|
|
5964
|
+
if (a === null || b === null) return a === b;
|
|
5965
|
+
return a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
|
|
5966
|
+
}
|
|
5967
|
+
function sameValue(a, b) {
|
|
5968
|
+
return toJsonText(a) === toJsonText(b);
|
|
5969
|
+
}
|
|
5970
|
+
var short2 = (sha) => sha.slice(0, 7);
|
|
5971
|
+
var datePart = (iso) => {
|
|
5972
|
+
const d = new Date(iso);
|
|
5973
|
+
return Number.isNaN(d.getTime()) ? "" : utcDate2(d);
|
|
5974
|
+
};
|
|
5975
|
+
function utcDate2(d) {
|
|
5976
|
+
return d.toISOString().slice(0, 10);
|
|
5977
|
+
}
|
|
5978
|
+
function nameOf(trailer) {
|
|
5979
|
+
const lt = trailer.lastIndexOf("<");
|
|
5980
|
+
return (lt === -1 ? trailer : trailer.slice(0, lt)).trim();
|
|
5981
|
+
}
|
|
5982
|
+
function emailOf(trailer) {
|
|
5983
|
+
const m = /<([^>]*)>\s*$/.exec(trailer);
|
|
5984
|
+
return m?.[1]?.trim() ?? null;
|
|
5985
|
+
}
|
|
5986
|
+
var MAX_STDERR_BYTES = 16 * 1024;
|
|
5987
|
+
var GIT_CONCURRENCY = Math.min(8, availableParallelism());
|
|
5988
|
+
function text(out) {
|
|
5989
|
+
try {
|
|
5990
|
+
return out.raw.toString("utf8");
|
|
5991
|
+
} catch {
|
|
5992
|
+
throw new HistoryTooLarge();
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
function runGit(args, cwd, stdin, maxBytes) {
|
|
5996
|
+
return new Promise((settle2, reject) => {
|
|
5997
|
+
let done = false;
|
|
5998
|
+
const finish = (out) => {
|
|
5999
|
+
if (done) return;
|
|
6000
|
+
done = true;
|
|
6001
|
+
settle2(out);
|
|
6002
|
+
};
|
|
6003
|
+
const fail = (err) => {
|
|
6004
|
+
if (done) return;
|
|
6005
|
+
done = true;
|
|
6006
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6007
|
+
};
|
|
6008
|
+
const failed = (missing) => ({
|
|
6009
|
+
code: null,
|
|
6010
|
+
raw: Buffer.alloc(0),
|
|
6011
|
+
missing,
|
|
6012
|
+
tooLarge: false,
|
|
6013
|
+
stderr: ""
|
|
6014
|
+
});
|
|
6015
|
+
let child;
|
|
6016
|
+
try {
|
|
6017
|
+
child = spawn2("git", args, { cwd, windowsHide: true });
|
|
6018
|
+
} catch {
|
|
6019
|
+
finish(failed(true));
|
|
6020
|
+
return;
|
|
6021
|
+
}
|
|
6022
|
+
let chunks = [];
|
|
6023
|
+
let total = 0;
|
|
6024
|
+
let tooLarge = false;
|
|
6025
|
+
child.stdout.on("data", (chunk) => {
|
|
6026
|
+
if (tooLarge) return;
|
|
6027
|
+
total += chunk.length;
|
|
6028
|
+
if (total > maxBytes) {
|
|
6029
|
+
tooLarge = true;
|
|
6030
|
+
chunks = [];
|
|
6031
|
+
child.kill();
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
chunks.push(chunk);
|
|
6035
|
+
});
|
|
6036
|
+
const errChunks = [];
|
|
6037
|
+
let errTotal = 0;
|
|
6038
|
+
child.stderr.on("data", (chunk) => {
|
|
6039
|
+
if (errTotal >= MAX_STDERR_BYTES) return;
|
|
6040
|
+
const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
|
|
6041
|
+
errTotal += kept.length;
|
|
6042
|
+
errChunks.push(kept);
|
|
6043
|
+
});
|
|
6044
|
+
child.on("error", (err) => {
|
|
6045
|
+
finish(failed(err.code === "ENOENT"));
|
|
6046
|
+
});
|
|
6047
|
+
child.stdin.on("error", () => {
|
|
6048
|
+
});
|
|
6049
|
+
child.on("close", (code) => {
|
|
6050
|
+
try {
|
|
6051
|
+
const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
|
|
6052
|
+
const stderr = Buffer.concat(errChunks).toString("utf8");
|
|
6053
|
+
finish({ code, raw, missing: false, tooLarge, stderr });
|
|
6054
|
+
} catch (err) {
|
|
6055
|
+
fail(err);
|
|
6056
|
+
}
|
|
6057
|
+
});
|
|
6058
|
+
if (stdin === void 0) child.stdin.end();
|
|
6059
|
+
else child.stdin.end(stdin);
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
|
|
6063
|
+
// src/meta/core/derive/index.ts
|
|
6064
|
+
var FIELD_SOURCES = {
|
|
6065
|
+
created: ["git"],
|
|
6066
|
+
"last-updated": ["git"],
|
|
6067
|
+
authors: ["git"],
|
|
6068
|
+
owner: ["codeowners"],
|
|
6069
|
+
"reviewed-by": ["github", "gitlab", "git"],
|
|
6070
|
+
"last-reviewed": ["github", "gitlab", "git"],
|
|
6071
|
+
provenance: ["git"]
|
|
6072
|
+
};
|
|
6073
|
+
var REVIEW_SOURCES = ["github", "gitlab"];
|
|
6074
|
+
var HOST_NAME = {
|
|
6075
|
+
github: "GitHub",
|
|
6076
|
+
gitlab: "GitLab"
|
|
6077
|
+
};
|
|
6078
|
+
function sourcesFor(field, commands) {
|
|
6079
|
+
if (isBuiltinField(field)) return FIELD_SOURCES[field];
|
|
6080
|
+
if (commands !== void 0 && Object.hasOwn(commands, field)) return ["command"];
|
|
6081
|
+
return [];
|
|
6082
|
+
}
|
|
6083
|
+
function consultedSources(fields, sources, commands) {
|
|
6084
|
+
const needed = /* @__PURE__ */ new Set();
|
|
6085
|
+
for (const field of fields) {
|
|
6086
|
+
for (const s of sourcesFor(field, commands)) needed.add(s);
|
|
6087
|
+
}
|
|
6088
|
+
return sources.filter((s) => needed.has(s));
|
|
6089
|
+
}
|
|
6090
|
+
function assertSourcesAvailable(sources, hint) {
|
|
6091
|
+
for (const name of Object.keys(sources)) {
|
|
6092
|
+
const status = sources[name];
|
|
6093
|
+
if (status === void 0 || status.available) continue;
|
|
6094
|
+
const reason = status.reason ?? "it could not answer";
|
|
6095
|
+
throw new DocmetaError(
|
|
6096
|
+
`${name} source unavailable: ${reason}${hint === void 0 ? "" : `; ${hint}`}`
|
|
6097
|
+
);
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
async function deriveMetadata(inputs, ctx) {
|
|
6101
|
+
const consulted = new Set(consultedSources(ctx.fields, ctx.sources, ctx.commands));
|
|
6102
|
+
const sources = {};
|
|
6103
|
+
let git2 = /* @__PURE__ */ new Map();
|
|
6104
|
+
if (consulted.has("git")) {
|
|
6105
|
+
const result = await deriveFromGit(inputs, {
|
|
6106
|
+
cwd: ctx.cwd,
|
|
6107
|
+
now: ctx.now,
|
|
6108
|
+
fields: ctx.fields,
|
|
6109
|
+
...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
|
|
6110
|
+
...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
|
|
6111
|
+
});
|
|
6112
|
+
sources.git = result.status;
|
|
6113
|
+
git2 = result.records;
|
|
6114
|
+
}
|
|
6115
|
+
const provenance = /* @__PURE__ */ new Map();
|
|
6116
|
+
for (const [label, facts] of git2) {
|
|
6117
|
+
if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
|
|
6118
|
+
}
|
|
6119
|
+
const rootCache = /* @__PURE__ */ new Map();
|
|
6120
|
+
const rootOf = (absPath) => {
|
|
6121
|
+
const dir = dirname5(absPath);
|
|
6122
|
+
let root = rootCache.get(dir);
|
|
6123
|
+
if (root === void 0) {
|
|
6124
|
+
root = findGitRoot(dir);
|
|
6125
|
+
rootCache.set(dir, root);
|
|
6126
|
+
}
|
|
6127
|
+
return root;
|
|
6128
|
+
};
|
|
6129
|
+
const rootFor = (input) => git2.get(input.label)?.root ?? rootOf(input.absPath);
|
|
6130
|
+
let owners = /* @__PURE__ */ new Map();
|
|
6131
|
+
if (consulted.has("codeowners")) {
|
|
6132
|
+
const result = await deriveFromCodeowners(inputs, {
|
|
6133
|
+
explicit: ctx.codeowners,
|
|
6134
|
+
configDir: ctx.configDir,
|
|
6135
|
+
rootOf
|
|
6136
|
+
});
|
|
6137
|
+
sources.codeowners = result.status;
|
|
6138
|
+
owners = result.records;
|
|
6139
|
+
}
|
|
6140
|
+
const requested = REVIEW_SOURCES.filter((s) => consulted.has(s));
|
|
6141
|
+
let reviews = /* @__PURE__ */ new Map();
|
|
6142
|
+
if (requested.length > 0) {
|
|
6143
|
+
if (!consulted.has("git")) {
|
|
6144
|
+
for (const s of requested) {
|
|
6145
|
+
sources[s] = {
|
|
6146
|
+
available: false,
|
|
6147
|
+
reason: `the ${s} source needs the git source to find each document's commits; add git to sources`
|
|
6148
|
+
};
|
|
6149
|
+
}
|
|
6150
|
+
} else if (sources.git?.available === false) {
|
|
6151
|
+
for (const s of requested) {
|
|
6152
|
+
sources[s] = {
|
|
6153
|
+
available: false,
|
|
6154
|
+
reason: `the git source could not answer, so the ${s} source cannot locate any commit`
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
} else {
|
|
6158
|
+
const result = await deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested);
|
|
6159
|
+
Object.assign(sources, result.status);
|
|
6160
|
+
reviews = result.records;
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
let commanded = /* @__PURE__ */ new Map();
|
|
6164
|
+
const doomed = Object.values(sources).some((s) => !s.available);
|
|
6165
|
+
if (consulted.has("command") && ctx.commands !== void 0 && !doomed) {
|
|
6166
|
+
const wanted = new Set(ctx.fields);
|
|
6167
|
+
const commands = Object.fromEntries(
|
|
6168
|
+
Object.entries(ctx.commands).filter(([field]) => wanted.has(field))
|
|
6169
|
+
);
|
|
6170
|
+
if (Object.keys(commands).length > 0) {
|
|
6171
|
+
const result = await deriveFromCommands(inputs, commands, { cwd: ctx.configDir ?? ctx.cwd });
|
|
6172
|
+
sources.command = result.status;
|
|
6173
|
+
commanded = result.records;
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
const records = /* @__PURE__ */ new Map();
|
|
6177
|
+
for (const input of inputs) {
|
|
6178
|
+
const fields = {};
|
|
6179
|
+
const g = git2.get(input.label);
|
|
6180
|
+
const r = reviews.get(input.label);
|
|
6181
|
+
const c = commanded.get(input.label);
|
|
6182
|
+
for (const field of ctx.fields) {
|
|
6183
|
+
let value = null;
|
|
6184
|
+
for (const source of sourcesFor(field, ctx.commands)) {
|
|
6185
|
+
if (!consulted.has(source)) continue;
|
|
6186
|
+
const candidate = valueFrom(source, field, g, r, owners.get(input.label), c);
|
|
6187
|
+
if (candidate !== null) {
|
|
6188
|
+
value = candidate;
|
|
6189
|
+
break;
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6192
|
+
fields[field] = value;
|
|
6193
|
+
}
|
|
6194
|
+
records.set(input.label, { file: input.label, fields });
|
|
6195
|
+
}
|
|
6196
|
+
return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
|
|
6197
|
+
}
|
|
6198
|
+
function valueFrom(source, field, git2, reviews, owner, commanded) {
|
|
6199
|
+
switch (source) {
|
|
6200
|
+
case "git":
|
|
6201
|
+
if (git2 === void 0 || !isBuiltinField(field) || field === "owner") return null;
|
|
6202
|
+
return git2[field];
|
|
6203
|
+
case "command":
|
|
6204
|
+
return commanded?.[field] ?? null;
|
|
6205
|
+
case "codeowners":
|
|
6206
|
+
return field === "owner" ? owner ?? null : null;
|
|
6207
|
+
case "github":
|
|
6208
|
+
case "gitlab": {
|
|
6209
|
+
if (reviews === void 0) return null;
|
|
6210
|
+
if (field !== "reviewed-by" && field !== "last-reviewed") return null;
|
|
6211
|
+
const fact = reviews[field];
|
|
6212
|
+
return fact !== null && fact.source === source ? fact : null;
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
async function deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested) {
|
|
6217
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
6218
|
+
for (const input of inputs) {
|
|
6219
|
+
const root = rootFor(input);
|
|
6220
|
+
if (root === null) continue;
|
|
6221
|
+
const bucket = byRoot.get(root) ?? [];
|
|
6222
|
+
bucket.push(input);
|
|
6223
|
+
byRoot.set(root, bucket);
|
|
6224
|
+
}
|
|
6225
|
+
const status = {};
|
|
6226
|
+
const records = /* @__PURE__ */ new Map();
|
|
6227
|
+
const unavailable2 = (which, reason) => {
|
|
6228
|
+
for (const s of which) status[s] = { available: false, reason };
|
|
6229
|
+
return { status, records: /* @__PURE__ */ new Map() };
|
|
6230
|
+
};
|
|
6231
|
+
if (byRoot.size === 0) {
|
|
6232
|
+
return unavailable2(requested, "no git repository contains the documents");
|
|
6233
|
+
}
|
|
6234
|
+
const cacheDir = resolve5(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
|
|
6235
|
+
for (const [root, bucket] of byRoot) {
|
|
6236
|
+
const client = ctx.reviews ?? await createReviewClient(root, { cwd: ctx.cwd, cache: ctx.cache, cacheDir, requested });
|
|
6237
|
+
const identity = await client.detect();
|
|
6238
|
+
if (identity === null) {
|
|
6239
|
+
const s = await client.status();
|
|
6240
|
+
return unavailable2(requested, s.reason ?? "no origin remote to tell GitHub from GitLab");
|
|
6241
|
+
}
|
|
6242
|
+
const source = identity.kind;
|
|
6243
|
+
if (!requested.includes(source)) {
|
|
6244
|
+
return unavailable2(
|
|
6245
|
+
requested,
|
|
6246
|
+
`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`
|
|
6247
|
+
);
|
|
6248
|
+
}
|
|
6249
|
+
const result = await deriveFromReviews(
|
|
6250
|
+
bucket.map((input) => ({
|
|
6251
|
+
label: input.label,
|
|
6252
|
+
sha: git2.get(input.label)?.lastBodyCommit ?? null
|
|
6253
|
+
})),
|
|
6254
|
+
client
|
|
6255
|
+
);
|
|
6256
|
+
if (!result.status.available) {
|
|
6257
|
+
status[source] = result.status;
|
|
6258
|
+
return { status, records: /* @__PURE__ */ new Map() };
|
|
6259
|
+
}
|
|
6260
|
+
status[source] = result.status;
|
|
6261
|
+
for (const [label, facts] of result.records) records.set(label, facts);
|
|
6262
|
+
}
|
|
6263
|
+
return { status, records };
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
// src/meta/core/derive/table.ts
|
|
6267
|
+
var DERIVED_VIEW = "derived";
|
|
6268
|
+
var DERIVED_ROWS = "_derived_rows";
|
|
6269
|
+
var RESOLVED_VIEW = "resolved";
|
|
6270
|
+
var ORIGIN_COLUMN = "_origin";
|
|
6271
|
+
var SOURCES_COLUMN = "_sources";
|
|
6272
|
+
function derivedColumns(commands) {
|
|
6273
|
+
return ["_path", ...derivableFields(commands), SOURCES_COLUMN];
|
|
6274
|
+
}
|
|
6275
|
+
function mentionsDerived(sql) {
|
|
6276
|
+
return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
|
|
6277
|
+
}
|
|
6278
|
+
function fieldsForSql(sql, fields) {
|
|
6279
|
+
const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
|
|
6280
|
+
if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
|
|
6281
|
+
return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
|
|
6282
|
+
}
|
|
6283
|
+
return fields.filter(named);
|
|
6284
|
+
}
|
|
6285
|
+
function escapeRegExp(s) {
|
|
6286
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6287
|
+
}
|
|
6288
|
+
function createDerivedView(db, records, columns) {
|
|
6289
|
+
const quoted = columns.map(quoteIdent);
|
|
6290
|
+
db.exec(
|
|
6291
|
+
`CREATE TABLE ${DERIVED_ROWS} (${quoted.map((c, i) => i === 0 ? `${c} TEXT PRIMARY KEY` : `${c} TEXT`).join(", ")})`
|
|
6292
|
+
);
|
|
6293
|
+
const insert = db.prepare(
|
|
6294
|
+
`INSERT INTO ${DERIVED_ROWS} VALUES (${quoted.map(() => "?").join(", ")})`
|
|
6295
|
+
);
|
|
6296
|
+
const fields = columns.slice(1, -1);
|
|
6297
|
+
db.exec("BEGIN");
|
|
6298
|
+
let committed = false;
|
|
6299
|
+
try {
|
|
6300
|
+
for (const [label, record] of records) {
|
|
6301
|
+
const sources = {};
|
|
6302
|
+
const values = fields.map((field) => {
|
|
6303
|
+
const derived = record.fields[field];
|
|
6304
|
+
if (derived == null) return null;
|
|
6305
|
+
sources[field] = { source: derived.source, evidence: derived.evidence };
|
|
6306
|
+
return bindValue(derived.value);
|
|
6307
|
+
});
|
|
6308
|
+
insert.run(label, ...values, JSON.stringify(sources));
|
|
6309
|
+
}
|
|
6310
|
+
db.exec("COMMIT");
|
|
6311
|
+
committed = true;
|
|
6312
|
+
} finally {
|
|
6313
|
+
if (!committed) db.exec("ROLLBACK");
|
|
6314
|
+
}
|
|
6315
|
+
db.exec(`CREATE VIEW ${DERIVED_VIEW} AS SELECT * FROM ${DERIVED_ROWS}`);
|
|
6316
|
+
}
|
|
6317
|
+
function resolvedColumns(docsColumns, commands) {
|
|
6318
|
+
const columns = ["_path"];
|
|
6319
|
+
const seen = /* @__PURE__ */ new Set(["_path", ORIGIN_COLUMN, SOURCES_COLUMN]);
|
|
6320
|
+
for (const column of docsColumns) {
|
|
6321
|
+
if (seen.has(column) || RESERVED.has(column)) continue;
|
|
6322
|
+
seen.add(column);
|
|
6323
|
+
columns.push(column);
|
|
6324
|
+
}
|
|
6325
|
+
for (const field of derivableFields(commands)) {
|
|
6326
|
+
if (seen.has(field)) continue;
|
|
6327
|
+
seen.add(field);
|
|
6328
|
+
columns.push(field);
|
|
6329
|
+
}
|
|
6330
|
+
columns.push(ORIGIN_COLUMN, SOURCES_COLUMN);
|
|
6331
|
+
return columns;
|
|
6332
|
+
}
|
|
6333
|
+
function mentionsResolved(sql) {
|
|
6334
|
+
return /\b(?:from|join|update|into|table|view)\s+(?:"resolved"|resolved\b)/i.test(sql);
|
|
6335
|
+
}
|
|
6336
|
+
function jsonPath(field) {
|
|
6337
|
+
return `$.${JSON.stringify(field)}`;
|
|
6338
|
+
}
|
|
6339
|
+
function sqlString(value) {
|
|
6340
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
6341
|
+
}
|
|
6342
|
+
function createResolvedView(db, docsColumns, commands) {
|
|
6343
|
+
const columns = resolvedColumns(docsColumns, commands);
|
|
6344
|
+
const fields = columns.slice(1, -2);
|
|
6345
|
+
const inDocs = new Set(docsColumns);
|
|
6346
|
+
const inDerived = new Set(derivableFields(commands));
|
|
6347
|
+
const selects = [`d.${quoteIdent("_path")} AS ${quoteIdent("_path")}`];
|
|
6348
|
+
const origin = [];
|
|
6349
|
+
for (const field of fields) {
|
|
6350
|
+
const path = sqlString(jsonPath(field));
|
|
6351
|
+
const asserted = `json_type(d.${quoteIdent("_data")}, ${path}) IS NOT NULL`;
|
|
6352
|
+
const assertedValue = inDocs.has(field) ? `d.${quoteIdent(field)}` : `d.${quoteIdent("_data")} ->> ${path}`;
|
|
6353
|
+
if (inDerived.has(field)) {
|
|
6354
|
+
const derivedValue = `r.${quoteIdent(field)}`;
|
|
6355
|
+
selects.push(
|
|
6356
|
+
`CASE WHEN ${asserted} THEN ${assertedValue} ELSE ${derivedValue} END AS ${quoteIdent(field)}`
|
|
6357
|
+
);
|
|
6358
|
+
origin.push(
|
|
6359
|
+
`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' WHEN ${derivedValue} IS NOT NULL THEN 'derived' END`
|
|
6360
|
+
);
|
|
6361
|
+
} else {
|
|
6362
|
+
selects.push(`${assertedValue} AS ${quoteIdent(field)}`);
|
|
6363
|
+
origin.push(`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' END`);
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
selects.push(
|
|
6367
|
+
`json_patch('{}', json_object(${origin.join(", ")})) AS ${quoteIdent(ORIGIN_COLUMN)}`
|
|
6368
|
+
);
|
|
6369
|
+
selects.push(`r.${quoteIdent(SOURCES_COLUMN)} AS ${quoteIdent(SOURCES_COLUMN)}`);
|
|
6370
|
+
db.exec(
|
|
6371
|
+
`CREATE VIEW ${RESOLVED_VIEW} AS SELECT ${selects.join(", ")} FROM docs d LEFT JOIN ${DERIVED_ROWS} r USING (${quoteIdent("_path")})`
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
async function deriveForTable(inputs, ctx, hint, fields) {
|
|
6375
|
+
const derive = ctx.config?.derive;
|
|
6376
|
+
const commands = commandsOf(derive);
|
|
6377
|
+
const managesProvenance = derive?.fields?.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
|
|
6378
|
+
const result = await deriveMetadata(inputs, {
|
|
6379
|
+
cwd: ctx.cwd,
|
|
6380
|
+
base: ctx.base,
|
|
6381
|
+
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
6382
|
+
sources: derive?.sources ?? [...DERIVE_SOURCES],
|
|
6383
|
+
fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
|
|
6384
|
+
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
6385
|
+
...commands !== void 0 ? { commands } : {},
|
|
6386
|
+
cache: ctx.cache,
|
|
6387
|
+
now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
|
|
6388
|
+
machines: machinesOf(derive)
|
|
6389
|
+
});
|
|
6390
|
+
assertSourcesAvailable(result.sources, hint);
|
|
6391
|
+
return result.records;
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6394
|
+
// src/meta/core/checks.ts
|
|
6395
|
+
var CHECK_KEYWORD = "check";
|
|
6396
|
+
function checkSchemaRef(name) {
|
|
6397
|
+
if (!CHECK_NAME.test(name) || name.toLowerCase().endsWith(".json")) {
|
|
6398
|
+
throw new DocmetaError(
|
|
6399
|
+
`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>).`
|
|
6400
|
+
);
|
|
6401
|
+
}
|
|
6402
|
+
return `check:${name}`;
|
|
6403
|
+
}
|
|
6404
|
+
var CONVENTION_COLUMNS = /* @__PURE__ */ new Set(["path", "line", "key", "message"]);
|
|
6405
|
+
function rowsToFindings(name, columns, rows, loaded) {
|
|
6406
|
+
if (!columns.includes("path")) {
|
|
6407
|
+
throw new DocmetaError(
|
|
6408
|
+
`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.`
|
|
6409
|
+
);
|
|
6410
|
+
}
|
|
6411
|
+
const findings = /* @__PURE__ */ new Map();
|
|
6412
|
+
const extras = columns.filter((c) => !CONVENTION_COLUMNS.has(c));
|
|
6413
|
+
for (const row of rows) {
|
|
6414
|
+
const path = row.path;
|
|
6415
|
+
if (typeof path !== "string" || loaded !== void 0 && !loaded.has(path)) {
|
|
6416
|
+
throw new DocmetaError(
|
|
6417
|
+
`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.`
|
|
6418
|
+
);
|
|
6419
|
+
}
|
|
6420
|
+
const key = typeof row.key === "string" && row.key !== "" ? row.key : void 0;
|
|
6421
|
+
const line = asLine(row.line);
|
|
6422
|
+
const message = typeof row.message === "string" && row.message !== "" ? row.message : synthesizeMessage(extras, row);
|
|
6423
|
+
const err = {
|
|
6424
|
+
schema: checkSchemaRef(name),
|
|
6425
|
+
keyword: CHECK_KEYWORD,
|
|
6426
|
+
// RFC 6901 escaping, or a key containing `/` or `~` would build a
|
|
6427
|
+
// pointer that parses as a different (nested) location.
|
|
6428
|
+
instancePath: key === void 0 ? "" : `/${escapePointerSegment(key)}`,
|
|
6429
|
+
message,
|
|
6430
|
+
...line === void 0 ? {} : { line }
|
|
6431
|
+
};
|
|
6432
|
+
const bucket = findings.get(path);
|
|
6433
|
+
if (bucket) bucket.push(err);
|
|
6434
|
+
else findings.set(path, [err]);
|
|
6435
|
+
}
|
|
6436
|
+
return findings;
|
|
6437
|
+
}
|
|
6438
|
+
function asLine(value) {
|
|
6439
|
+
const n = typeof value === "bigint" ? Number(value) : typeof value === "number" ? value : void 0;
|
|
6440
|
+
return n !== void 0 && Number.isInteger(n) && n > 0 ? n : void 0;
|
|
6441
|
+
}
|
|
6442
|
+
function synthesizeMessage(extras, row) {
|
|
6443
|
+
const pairs = extras.flatMap((c) => {
|
|
6444
|
+
const value = row[c];
|
|
6445
|
+
if (value === null || value === void 0) return [];
|
|
6446
|
+
const text2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : JSON.stringify(value);
|
|
6447
|
+
return [`${c}=${text2}`];
|
|
6448
|
+
});
|
|
6449
|
+
return pairs.length > 0 ? pairs.join(", ") : "check matched";
|
|
6450
|
+
}
|
|
6451
|
+
async function runChecks(checks, entries, ctx = {}) {
|
|
6452
|
+
const merged = /* @__PURE__ */ new Map();
|
|
6453
|
+
if (checks.length === 0) return merged;
|
|
6454
|
+
const { DatabaseSync } = await loadSqlite();
|
|
6455
|
+
const db = new DatabaseSync(":memory:");
|
|
6456
|
+
try {
|
|
6457
|
+
const dataColumns = corpusDataColumns(entries);
|
|
6458
|
+
createDocsTable(db, entries, dataColumns);
|
|
6459
|
+
registerLineFor(db, entries);
|
|
6460
|
+
createCollectionViews(db, collectCollections(entries, ctx));
|
|
6461
|
+
const wantsDerived = checks.filter(
|
|
6462
|
+
(c) => mentionsDerived(c.query) || mentionsResolved(c.query)
|
|
6463
|
+
);
|
|
6464
|
+
if (wantsDerived.length > 0) {
|
|
6465
|
+
if (ctx.derive === void 0) {
|
|
6466
|
+
throw new DocmetaError(
|
|
6467
|
+
`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.`
|
|
6468
|
+
);
|
|
6469
|
+
}
|
|
6470
|
+
const commands = ctx.commands;
|
|
6471
|
+
const readable = derivableFields(commands);
|
|
6472
|
+
const fields = /* @__PURE__ */ new Set();
|
|
6473
|
+
for (const c of wantsDerived) for (const f of fieldsForSql(c.query, readable)) fields.add(f);
|
|
6474
|
+
createDerivedView(db, await ctx.derive([...fields]), derivedColumns(commands));
|
|
6475
|
+
createResolvedView(db, dataColumns, commands);
|
|
6476
|
+
}
|
|
6477
|
+
db.exec("PRAGMA query_only = 1");
|
|
6478
|
+
const loaded = new Set(entries.map((e) => e.label));
|
|
6479
|
+
for (const check of checks) {
|
|
6480
|
+
const head = stripLeadingTrivia(check.query);
|
|
6481
|
+
if (/^(attach|vacuum)\b/i.test(head)) {
|
|
6482
|
+
throw new DocmetaError(
|
|
6483
|
+
`check "${check.name}": ${head.split(/\s/, 1)[0]?.toUpperCase() ?? "that statement"} is refused: it can write outside the docs table.`
|
|
6484
|
+
);
|
|
6485
|
+
}
|
|
6486
|
+
let tokens2;
|
|
6487
|
+
try {
|
|
6488
|
+
tokens2 = collectNamedParameters(check.query);
|
|
6489
|
+
} catch (err) {
|
|
6490
|
+
throw new DocmetaError(
|
|
6491
|
+
`check "${check.name}": ${errorMessage(err)}`
|
|
6492
|
+
);
|
|
6493
|
+
}
|
|
6494
|
+
if (tokens2.length > 0) {
|
|
6495
|
+
throw new DocmetaError(
|
|
6496
|
+
`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.`
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
let columns;
|
|
6500
|
+
let rows;
|
|
6501
|
+
try {
|
|
6502
|
+
assertSingleStatement(check.query);
|
|
6503
|
+
const stmt = db.prepare(check.query);
|
|
6504
|
+
columns = stmt.columns().map((c) => c.name);
|
|
6505
|
+
rows = stmt.all();
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
const message = errorMessage(err);
|
|
6508
|
+
throw new DocmetaError(
|
|
6509
|
+
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}`
|
|
6510
|
+
);
|
|
6511
|
+
}
|
|
6512
|
+
for (const [path, errs] of rowsToFindings(
|
|
6513
|
+
check.name,
|
|
6514
|
+
columns,
|
|
6515
|
+
rows,
|
|
6516
|
+
loaded
|
|
6517
|
+
)) {
|
|
6518
|
+
const bucket = merged.get(path);
|
|
6519
|
+
if (bucket) bucket.push(...errs);
|
|
6520
|
+
else merged.set(path, errs);
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
} finally {
|
|
6524
|
+
db.close();
|
|
6525
|
+
}
|
|
6526
|
+
return merged;
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
// src/meta/core/derive/provenance-place.ts
|
|
6530
|
+
import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve6, sep as sep6 } from "node:path";
|
|
6531
|
+
var toPosix3 = (p) => p.split(sep6).join("/");
|
|
6532
|
+
function provenanceManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
|
|
6533
|
+
const out = [];
|
|
6534
|
+
for (const collection of collections) {
|
|
6535
|
+
for (const manifest of collection.externalMetadata) {
|
|
6536
|
+
const implied = manifest.keys === void 0;
|
|
6537
|
+
if (!implied && manifest.keys?.includes(field) !== true) continue;
|
|
6538
|
+
const url = classifyRef(manifest.file).kind === "url";
|
|
6539
|
+
if (url && !implied) throw new DocmetaError(urlRefusal(collection.name, field));
|
|
6540
|
+
if (url) {
|
|
6541
|
+
out.push({
|
|
6542
|
+
collection: collection.name,
|
|
6543
|
+
absPath: manifest.file,
|
|
6544
|
+
file: manifest.file,
|
|
6545
|
+
join: externalMetadataJoin(manifest),
|
|
6546
|
+
perPage: false,
|
|
6547
|
+
written: manifest.file,
|
|
6548
|
+
implied: { collection, manifest, url }
|
|
6549
|
+
});
|
|
6550
|
+
continue;
|
|
6551
|
+
}
|
|
6552
|
+
const absPath = isAbsolute5(manifest.file) ? manifest.file : resolve6(configDir, manifest.file);
|
|
6553
|
+
const perPage = hasPagePlaceholder(manifest.file);
|
|
6554
|
+
out.push({
|
|
6555
|
+
collection: collection.name,
|
|
6556
|
+
absPath,
|
|
6557
|
+
file: perPage ? manifest.file : reportedPath(absPath, base),
|
|
6558
|
+
join: externalMetadataJoin(manifest),
|
|
6559
|
+
perPage,
|
|
6560
|
+
written: manifest.file,
|
|
6561
|
+
...implied ? { implied: { collection, manifest, url } } : {}
|
|
6562
|
+
});
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
return out;
|
|
6566
|
+
}
|
|
6567
|
+
function urlRefusal(collection, field) {
|
|
6568
|
+
return `collection ${collection}: ${field} cannot come from a URL manifest, because manni meta derive writes it.`;
|
|
6569
|
+
}
|
|
6570
|
+
function provenancePlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
|
|
6571
|
+
if (manifests.length === 0) return void 0;
|
|
6572
|
+
const members = new Set(memberOf(collections, configDir, base, label));
|
|
6573
|
+
const owning = manifests.filter(
|
|
6574
|
+
(m) => members.has(m.collection) && (m.implied === void 0 || ownsKey(m.implied.collection, m.implied.manifest, field, marked))
|
|
6575
|
+
);
|
|
6576
|
+
const [declared, second] = owning;
|
|
6577
|
+
if (declared === void 0) return void 0;
|
|
6578
|
+
if (second !== void 0) {
|
|
6579
|
+
throw new DocmetaError(
|
|
6580
|
+
`${label} is in collections ${declared.collection} and ${second.collection}, and both keep ${field} in a manifest.`
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
if (declared.implied?.url === true) throw new DocmetaError(urlRefusal(declared.collection, field));
|
|
6584
|
+
const manifest = declared.perPage ? ownManifest(declared, label, collections, configDir, base) : declared;
|
|
6585
|
+
if (manifest.join === PATH_JOIN) {
|
|
6586
|
+
const entry = toPosix3(relative7(configDir, resolve6(base, label)));
|
|
6587
|
+
return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
|
|
6588
|
+
}
|
|
6589
|
+
const value = data[manifest.join];
|
|
6590
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
6591
|
+
throw new DocmetaError(
|
|
6592
|
+
`${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${field} has no entry there.`
|
|
6593
|
+
);
|
|
6594
|
+
}
|
|
6595
|
+
return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
|
|
6596
|
+
}
|
|
6597
|
+
function ownManifest(manifest, label, collections, configDir, base) {
|
|
6598
|
+
const resolved = pageManifestPath(manifest.written, configDir, resolve6(base, label));
|
|
6599
|
+
if ("outside" in resolved) {
|
|
6600
|
+
const declared = collections.find((c) => c.name === manifest.collection)?.externalMetadata.find((m) => m.file === manifest.written);
|
|
6601
|
+
throw new DocmetaError(
|
|
6602
|
+
outsideRefusal(declared ?? { file: manifest.written }, manifest.collection, resolved.pageRel)
|
|
6603
|
+
);
|
|
6604
|
+
}
|
|
6605
|
+
return { ...manifest, absPath: resolved.abs, file: reportedPath(resolved.abs, base) };
|
|
6606
|
+
}
|
|
6607
|
+
function readerManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
|
|
6608
|
+
const local = collections.map((c) => ({
|
|
6609
|
+
...c,
|
|
6610
|
+
externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
|
|
6611
|
+
}));
|
|
6612
|
+
return provenanceManifests(local, configDir, base, field);
|
|
6613
|
+
}
|
|
6614
|
+
function readerPlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
|
|
6615
|
+
try {
|
|
6616
|
+
const place = provenancePlace(label, data, manifests, collections, configDir, base, marked, field);
|
|
6617
|
+
return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
|
|
6618
|
+
} catch (err) {
|
|
6619
|
+
if (err instanceof DocmetaError) return void 0;
|
|
6620
|
+
throw err;
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
function stampManifests(label, data, collections, configDir, base, marked, fields) {
|
|
6624
|
+
const out = {};
|
|
6625
|
+
for (const field of STAMPED_DATE_FIELDS) {
|
|
6626
|
+
if (!fields.includes(field)) continue;
|
|
6627
|
+
const manifests = readerManifests(collections, configDir, base, field);
|
|
6628
|
+
const place = readerPlace(label, data, manifests, collections, configDir, base, marked, field);
|
|
6629
|
+
if (place !== void 0) out[field] = place;
|
|
6630
|
+
}
|
|
6631
|
+
return out;
|
|
6632
|
+
}
|
|
6633
|
+
|
|
6634
|
+
// src/shared/cli-options.ts
|
|
6635
|
+
import { basename as basename4, relative as relative8 } from "node:path";
|
|
6636
|
+
function collect(value, prev) {
|
|
6637
|
+
return prev.concat([value]);
|
|
6638
|
+
}
|
|
6639
|
+
function splitList(value) {
|
|
6640
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6641
|
+
}
|
|
6642
|
+
var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
|
|
6643
|
+
async function readStdin() {
|
|
6644
|
+
const chunks = [];
|
|
6645
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6646
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
6647
|
+
}
|
|
6648
|
+
function configOption(value) {
|
|
6649
|
+
if (value === false) return { noConfig: true };
|
|
6650
|
+
return typeof value === "string" ? { configPath: value } : {};
|
|
6651
|
+
}
|
|
6652
|
+
function explicitFalse(value) {
|
|
6653
|
+
return value === false ? false : void 0;
|
|
6654
|
+
}
|
|
6655
|
+
function reportConfig(toStdout, cwd) {
|
|
6656
|
+
return (info) => {
|
|
6657
|
+
const where = (relative8(cwd, info.dir) || ".").replace(/\\/g, "/");
|
|
6658
|
+
const line = `Using ${basename4(info.path)} (${where})
|
|
6659
|
+
`;
|
|
6660
|
+
(toStdout ? process.stdout : process.stderr).write(line);
|
|
6661
|
+
};
|
|
6662
|
+
}
|
|
6663
|
+
|
|
6664
|
+
// src/meta/commands/validate.ts
|
|
6665
|
+
function parseErrorResult(file, format, message, keyword) {
|
|
6666
|
+
const err = {
|
|
6667
|
+
schema: "(parse)",
|
|
6668
|
+
instancePath: "",
|
|
6669
|
+
message,
|
|
6670
|
+
keyword
|
|
6671
|
+
};
|
|
6672
|
+
return { file, format, ok: false, schemas: [], errors: [err] };
|
|
6673
|
+
}
|
|
6674
|
+
function once(fn) {
|
|
6675
|
+
let pending;
|
|
6676
|
+
return () => {
|
|
6677
|
+
pending ??= fn();
|
|
6678
|
+
return pending;
|
|
6679
|
+
};
|
|
6680
|
+
}
|
|
6681
|
+
async function runValidate(opts) {
|
|
6682
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
6683
|
+
const run2 = await resolveRunConfig({
|
|
6684
|
+
cwd,
|
|
6685
|
+
configPath: opts.configPath,
|
|
6686
|
+
noConfig: opts.noConfig,
|
|
6687
|
+
inputs: opts.inputs,
|
|
6688
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
6689
|
+
onConfigLoaded: opts.onConfigLoaded
|
|
6690
|
+
});
|
|
6691
|
+
const {
|
|
6692
|
+
config,
|
|
6693
|
+
inputs,
|
|
6694
|
+
base,
|
|
6695
|
+
configDir,
|
|
6696
|
+
collections,
|
|
6697
|
+
declaredCollections,
|
|
6698
|
+
fromCollections,
|
|
6699
|
+
configFile
|
|
6700
|
+
} = run2;
|
|
6701
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
6702
|
+
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
6703
|
+
if (inputs.length === 0) {
|
|
6704
|
+
throw new DocmetaError(
|
|
6705
|
+
"No files to validate. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
|
|
6709
|
+
if (opts.as && !forcedExtractor) {
|
|
6710
|
+
throw new DocmetaError(
|
|
6711
|
+
`Unknown format "${opts.as}". Supported extensions: ${supportedExtensions().join(", ")}.`
|
|
6712
|
+
);
|
|
6713
|
+
}
|
|
6714
|
+
const exts = opts.exts ?? (forcedExtractor ? forcedExtractor.extensions : void 0);
|
|
6715
|
+
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
6716
|
+
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
6717
|
+
const exclude = opts.exclude ?? [];
|
|
6718
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
6719
|
+
inputs: fileInputs,
|
|
6720
|
+
exts,
|
|
6721
|
+
exclude,
|
|
6722
|
+
cwd: base,
|
|
6723
|
+
allowEmpty,
|
|
6724
|
+
...gitignoreOptions({
|
|
6725
|
+
flag: opts.respectGitignore,
|
|
6726
|
+
configured: config?.respectGitignore,
|
|
6727
|
+
onNotice: opts.onNotice
|
|
6728
|
+
})
|
|
6729
|
+
});
|
|
6730
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
6731
|
+
assertNonEmpty({
|
|
6732
|
+
files,
|
|
6733
|
+
inputs: fileInputs,
|
|
6734
|
+
usingStdin,
|
|
6735
|
+
allowEmpty,
|
|
6736
|
+
exclude,
|
|
6737
|
+
exts,
|
|
6738
|
+
gitignoreSkipped,
|
|
6739
|
+
action: "validated"
|
|
6740
|
+
});
|
|
6741
|
+
const trustRoot = schemaTrustRoot(cwd, configDir);
|
|
6742
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
6743
|
+
configDir: configDir ?? cwd,
|
|
6744
|
+
base,
|
|
6745
|
+
offline: opts.offline ?? config?.offline ?? false,
|
|
6746
|
+
// A `{page}` manifest (0058) is read for exactly the pages this run reads.
|
|
6747
|
+
pages: files.map((file) => resolve7(base, file))
|
|
6748
|
+
});
|
|
6749
|
+
const validator = new Validator(
|
|
6750
|
+
schemaLoadOptions({
|
|
6751
|
+
// The config's directory when a config governs the run, so one project
|
|
6752
|
+
// keeps one cache no matter which directory the command was run from.
|
|
6753
|
+
root: configDir ?? cwd,
|
|
6754
|
+
// A relative file ref belongs to the run's directory, not the cache root.
|
|
6755
|
+
fileBase: cwd,
|
|
6756
|
+
ttlHours: config?.schemaCache?.ttlHours,
|
|
6757
|
+
offline: opts.offline ?? config?.offline,
|
|
6758
|
+
// Built from the rebased config, so a pinned local ref is keyed by the
|
|
6759
|
+
// same absolute spelling `resolveSchemaSet` will hand to `loadSchema`.
|
|
6760
|
+
pins: collectSchemaPins(config),
|
|
6761
|
+
registered: config?.registered
|
|
6762
|
+
})
|
|
6763
|
+
);
|
|
6764
|
+
const marks = pageMarks({
|
|
6765
|
+
validator,
|
|
6766
|
+
config,
|
|
6767
|
+
cwd,
|
|
6768
|
+
trustRoot,
|
|
6769
|
+
...opts.cliSchemas !== void 0 ? { cliSchemas: opts.cliSchemas } : {}
|
|
6770
|
+
});
|
|
6771
|
+
let relocation;
|
|
6772
|
+
const relocationCtx = () => relocation ??= relocationContext(run2, {
|
|
6773
|
+
cwd,
|
|
6774
|
+
targets: fromCollections ? [] : opts.inputs,
|
|
6775
|
+
validator,
|
|
6776
|
+
...opts.noConfig !== void 0 ? { noConfig: opts.noConfig } : {},
|
|
6777
|
+
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
6778
|
+
});
|
|
6779
|
+
const refFrame = { cwd, base: configDir ?? cwd, runBase: base };
|
|
6780
|
+
const locationFindings = async (label, own, data, refs, members, merged, lineFor) => {
|
|
6781
|
+
const preferences = await validator.locationPreferences(data, refs);
|
|
6782
|
+
if (preferences.size === 0) return [];
|
|
6783
|
+
const owned = new Set(merged.collisions.map((c) => c.key));
|
|
6784
|
+
const declaredMembers = declaredCollections === collections ? members : memberOf(declaredCollections, configDir ?? cwd, base, label);
|
|
6785
|
+
const joins = new Set(
|
|
6786
|
+
declaredCollections.filter((c) => declaredMembers.includes(c.name)).flatMap((c) => c.externalMetadata.map(externalMetadataJoin)).filter((join5) => join5 !== PATH_JOIN)
|
|
6787
|
+
);
|
|
6788
|
+
const findings = [];
|
|
6789
|
+
for (const key of Object.keys(data)) {
|
|
6790
|
+
const preference = preferences.get(key);
|
|
6791
|
+
if (preference === void 0) continue;
|
|
6792
|
+
const ref = canonicalSchemaRef(preference.schema, refFrame);
|
|
6793
|
+
const pointer = externalMetadataPointer(key);
|
|
6794
|
+
const common = {
|
|
6795
|
+
keyword: LOCATION_FINDING_KEYWORD,
|
|
6796
|
+
subject: key,
|
|
6797
|
+
instancePath: pointer,
|
|
6798
|
+
severity: "warning"
|
|
6799
|
+
};
|
|
6800
|
+
if (Object.hasOwn(own, key)) {
|
|
6801
|
+
if (preference.location !== "external" || owned.has(key) || joins.has(key)) continue;
|
|
6802
|
+
const line = lineFor(key);
|
|
6803
|
+
const none = await homeless(label, own, key);
|
|
6804
|
+
findings.push({
|
|
6805
|
+
schema: LOCATION_EXTERNAL_SCHEMA,
|
|
6806
|
+
...common,
|
|
6807
|
+
// relocate refuses --no-config (U2), so it is no answer there.
|
|
6808
|
+
// Nor is it one for stdin, which relocate refuses: no advice there.
|
|
6809
|
+
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.`,
|
|
6810
|
+
...line != null ? { line } : {}
|
|
6811
|
+
});
|
|
6812
|
+
continue;
|
|
6813
|
+
}
|
|
6814
|
+
if (preference.location !== "page") continue;
|
|
6815
|
+
const at2 = merged.locate(pointer);
|
|
6816
|
+
if (at2 === void 0) continue;
|
|
6817
|
+
findings.push({
|
|
6818
|
+
schema: LOCATION_PAGE_SCHEMA,
|
|
6819
|
+
...common,
|
|
6820
|
+
// A fetched manifest cannot give a value up, so relocate is no
|
|
6821
|
+
// answer; the value moves in the repository that serves it.
|
|
6822
|
+
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.`,
|
|
6823
|
+
file: at2.file,
|
|
6824
|
+
...at2.line != null ? { line: at2.line } : {}
|
|
6825
|
+
});
|
|
6826
|
+
}
|
|
6827
|
+
return findings;
|
|
6828
|
+
};
|
|
6829
|
+
const homeless = async (label, own, key) => {
|
|
6830
|
+
if (label === STDIN_LABEL) return 0;
|
|
6831
|
+
const home = await keyHome(relocationCtx(), label, own, key);
|
|
6832
|
+
return home.kind === "unowned" && home.home.kind === "none" && home.home.reason === "collections" && home.home.collections > 1 ? home.home.collections : 0;
|
|
6833
|
+
};
|
|
6834
|
+
const results = [];
|
|
6835
|
+
const skippedDefaultOnly = /* @__PURE__ */ new Set();
|
|
6836
|
+
const defaultOnly = (label, members) => {
|
|
6837
|
+
try {
|
|
6838
|
+
return resolveSchemaSetWithSource({
|
|
6839
|
+
filePath: label,
|
|
6840
|
+
fileSchema: void 0,
|
|
6841
|
+
cliSchemas: opts.cliSchemas,
|
|
6842
|
+
config,
|
|
6843
|
+
memberOf: members,
|
|
6844
|
+
fileBase: cwd,
|
|
6845
|
+
trustRoot
|
|
6846
|
+
}).source === "default";
|
|
6847
|
+
} catch {
|
|
6848
|
+
return false;
|
|
6849
|
+
}
|
|
6850
|
+
};
|
|
6851
|
+
const joinHits = /* @__PURE__ */ new Map();
|
|
6852
|
+
const scopedToCollections = (opts.collections?.length ?? 0) > 0;
|
|
6853
|
+
const scopedByFlags = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
|
|
6854
|
+
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;
|
|
6855
|
+
const scoped = scopedByFlags || scopedToCollections;
|
|
6856
|
+
const configuredChecks = config?.checks ?? [];
|
|
6857
|
+
if (!scopedByFlags) {
|
|
6858
|
+
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
6859
|
+
const orphans = orphanEntries(externalMetadata, files, base, covered);
|
|
6860
|
+
if (orphans.length > 0) throw orphanError(orphans);
|
|
6861
|
+
}
|
|
6862
|
+
const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
|
|
6863
|
+
const checkEntries = [];
|
|
6864
|
+
const encryptionKey = lazyKey(configFile, opts.env);
|
|
6865
|
+
let unverified = 0;
|
|
6866
|
+
const deriveConfig = config?.derive;
|
|
6867
|
+
const configuredFields = managedFields(deriveConfig);
|
|
6868
|
+
const defaultedFields = deriveConfig !== void 0 && deriveConfig.fields === void 0;
|
|
6869
|
+
const deriveWillRun = deriveConfig !== void 0 && configuredFields.length > 0 && opts.derive !== false;
|
|
6870
|
+
const pageManaged = /* @__PURE__ */ new Map();
|
|
6871
|
+
const derivedChecks = checksWillRun ? configuredChecks.filter((c) => mentionsDerived(c.query) || mentionsResolved(c.query)) : [];
|
|
6872
|
+
const checksNeedDerived = derivedChecks.length > 0;
|
|
6873
|
+
const deriveInputs = [];
|
|
6874
|
+
const managedViews = /* @__PURE__ */ new Map();
|
|
6875
|
+
const keepDeriveInputs = deriveWillRun || checksNeedDerived;
|
|
6876
|
+
const deriveCommands = commandsOf(deriveConfig);
|
|
6877
|
+
const deriveFields = new Set(deriveWillRun && !defaultedFields ? configuredFields : []);
|
|
6878
|
+
const readable = derivableFields(deriveCommands);
|
|
6879
|
+
for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
|
|
6880
|
+
const provenanceManifests2 = keepDeriveInputs && (deriveFields.has(PROVENANCE_FIELD) || deriveWillRun && configuredFields.includes(PROVENANCE_FIELD)) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
|
|
6881
|
+
const derivedOnce = once(async () => {
|
|
6882
|
+
const derived = await deriveMetadata(deriveInputs, {
|
|
6883
|
+
cwd,
|
|
6884
|
+
base,
|
|
6885
|
+
...configDir !== void 0 ? { configDir } : {},
|
|
6886
|
+
sources: deriveConfig?.sources ?? [...DERIVE_SOURCES],
|
|
6887
|
+
fields: [...deriveFields],
|
|
6888
|
+
...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
|
|
6889
|
+
...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
|
|
6890
|
+
cache: opts.cache ?? true,
|
|
6891
|
+
now: opts.now ?? (() => /* @__PURE__ */ new Date()),
|
|
6892
|
+
machines: machinesOf(deriveConfig)
|
|
6893
|
+
});
|
|
6894
|
+
assertSourcesAvailable(
|
|
6895
|
+
derived.sources,
|
|
6896
|
+
deriveWillRun ? "pass --no-derive to skip the comparison, or narrow derive.sources" : "narrow derive.sources in manni.config.yaml"
|
|
6897
|
+
);
|
|
6898
|
+
for (const [name, status] of Object.entries(derived.sources)) {
|
|
6899
|
+
if (status.available && status.reason !== void 0) {
|
|
6900
|
+
opts.onNotice?.(`${name}: ${status.reason}`);
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
return derived;
|
|
6904
|
+
});
|
|
6905
|
+
const processOne = async (label, content, extension) => {
|
|
6906
|
+
const extractor = forcedExtractor ?? extractorForExtension(extension);
|
|
6907
|
+
if (!extractor) {
|
|
6908
|
+
throw new DocmetaError(
|
|
6909
|
+
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
6910
|
+
);
|
|
6911
|
+
}
|
|
6912
|
+
const members = membersFor(label);
|
|
6913
|
+
let extracted;
|
|
6914
|
+
try {
|
|
6915
|
+
extracted = extractor.extract(content, label, {
|
|
6916
|
+
elements: resolveElements(label, config, members)
|
|
6917
|
+
});
|
|
6918
|
+
} catch (err) {
|
|
6919
|
+
if (err instanceof DocmetaError) throw err;
|
|
6920
|
+
if (opts.skipDefaultOnly === true && defaultOnly(label, members)) {
|
|
6921
|
+
skippedDefaultOnly.add(label);
|
|
6922
|
+
return;
|
|
6923
|
+
}
|
|
6924
|
+
results.push(
|
|
6925
|
+
parseErrorResult(label, extractor.name, errorMessage(err), "parse")
|
|
6926
|
+
);
|
|
6927
|
+
return;
|
|
6928
|
+
}
|
|
6929
|
+
const merged = await mergeWithMarks(label, extracted, externalMetadata, members, base, {
|
|
6930
|
+
encryptionKey,
|
|
6931
|
+
marks
|
|
6932
|
+
});
|
|
6933
|
+
if (keepDeriveInputs && label !== STDIN_LABEL && (checksNeedDerived || deriveCovers(deriveConfig, members))) {
|
|
6934
|
+
const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base, merged.marked);
|
|
6935
|
+
const owned = declaredCollections.length === 0 ? {} : stampManifests(label, extracted.data, declaredCollections, configDir ?? cwd, base, merged.marked, STAMPED_DATE_FIELDS);
|
|
6936
|
+
deriveInputs.push({
|
|
6937
|
+
label,
|
|
6938
|
+
absPath: resolve7(base, label),
|
|
6939
|
+
content,
|
|
6940
|
+
extracted,
|
|
6941
|
+
...place !== void 0 ? { provenanceManifest: place } : {},
|
|
6942
|
+
...Object.keys(owned).length > 0 ? { stampManifests: owned } : {}
|
|
6943
|
+
});
|
|
6944
|
+
}
|
|
6945
|
+
const own = extracted.data;
|
|
6946
|
+
extracted = merged.extracted;
|
|
6947
|
+
if (deriveWillRun && label !== STDIN_LABEL) {
|
|
6948
|
+
managedViews.set(label, {
|
|
6949
|
+
data: extracted.data,
|
|
6950
|
+
marked: [],
|
|
6951
|
+
hidden: [],
|
|
6952
|
+
refs: [],
|
|
6953
|
+
locate: merged.locate
|
|
6954
|
+
});
|
|
6955
|
+
}
|
|
6956
|
+
for (const j of merged.joins) {
|
|
6957
|
+
const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
|
|
6958
|
+
const line = extracted.lineFor(j.field);
|
|
6959
|
+
const hits = byValue.get(j.value) ?? [];
|
|
6960
|
+
hits.push({
|
|
6961
|
+
label,
|
|
6962
|
+
file: j.file,
|
|
6963
|
+
shown: j.pageValue ?? j.value,
|
|
6964
|
+
...line != null ? { line } : {}
|
|
6965
|
+
});
|
|
6966
|
+
byValue.set(j.value, hits);
|
|
6967
|
+
joinHits.set(j.field, byValue);
|
|
6968
|
+
}
|
|
6969
|
+
if (checksWillRun) checkEntries.push({ label, extracted });
|
|
6970
|
+
let resolved;
|
|
6971
|
+
try {
|
|
6972
|
+
resolved = resolveSchemaSetWithSource({
|
|
6973
|
+
filePath: label,
|
|
6974
|
+
fileSchema: extracted.data[FILE_SCHEMA_KEY],
|
|
6975
|
+
cliSchemas: opts.cliSchemas,
|
|
6976
|
+
config,
|
|
6977
|
+
memberOf: members,
|
|
6978
|
+
// A document's own `$schema` is measured from the run's directory, the
|
|
6979
|
+
// same base `loadSchema` will read it from, and contained to the
|
|
6980
|
+
// repository the run is standing in.
|
|
6981
|
+
fileBase: cwd,
|
|
6982
|
+
trustRoot,
|
|
6983
|
+
onNotice: opts.onNotice
|
|
6984
|
+
});
|
|
6985
|
+
} catch (err) {
|
|
6986
|
+
results.push(
|
|
6987
|
+
parseErrorResult(label, extractor.name, errorMessage(err), "schema")
|
|
6988
|
+
);
|
|
6989
|
+
return;
|
|
6990
|
+
}
|
|
6991
|
+
if (opts.skipDefaultOnly === true && resolved.source === "default") {
|
|
6992
|
+
skippedDefaultOnly.add(label);
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
const schemaSet = resolved.schemas;
|
|
6996
|
+
if (deriveWillRun && defaultedFields && label !== STDIN_LABEL && deriveCovers(deriveConfig, members)) {
|
|
6997
|
+
let claimed = /* @__PURE__ */ new Set();
|
|
6998
|
+
try {
|
|
6999
|
+
claimed = await validator.claimedProperties(schemaSet);
|
|
7000
|
+
} catch (err) {
|
|
7001
|
+
if (!(err instanceof DocmetaError)) throw err;
|
|
7002
|
+
}
|
|
7003
|
+
const own2 = configuredFields.filter((f) => claimed.has(f));
|
|
7004
|
+
pageManaged.set(label, own2);
|
|
7005
|
+
for (const f of own2) deriveFields.add(f);
|
|
7006
|
+
}
|
|
7007
|
+
let errors;
|
|
7008
|
+
try {
|
|
7009
|
+
const view = await encryptionView({
|
|
7010
|
+
data: extracted.data,
|
|
7011
|
+
refs: schemaSet,
|
|
7012
|
+
validator,
|
|
7013
|
+
key: encryptionKey,
|
|
7014
|
+
locate: merged.locate
|
|
7015
|
+
});
|
|
7016
|
+
unverified += view.unverified.length;
|
|
7017
|
+
if (deriveWillRun && label !== STDIN_LABEL) {
|
|
7018
|
+
managedViews.set(label, {
|
|
7019
|
+
data: view.data,
|
|
7020
|
+
marked: view.marked,
|
|
7021
|
+
hidden: [...view.unreadable, ...view.unverified],
|
|
7022
|
+
refs: schemaSet,
|
|
7023
|
+
locate: merged.locate
|
|
7024
|
+
});
|
|
7025
|
+
}
|
|
7026
|
+
errors = [
|
|
7027
|
+
...settleFindings(
|
|
7028
|
+
await validator.validate(
|
|
7029
|
+
view.data,
|
|
7030
|
+
schemaSet,
|
|
7031
|
+
extracted.lineFor,
|
|
7032
|
+
extracted.colFor,
|
|
7033
|
+
merged.locate
|
|
7034
|
+
),
|
|
7035
|
+
view,
|
|
7036
|
+
extracted,
|
|
7037
|
+
merged.locate
|
|
7038
|
+
),
|
|
7039
|
+
...encryptionFindings(view, extracted, merged.locate),
|
|
7040
|
+
// Over the decrypted view, the object validation evaluated: a mark
|
|
7041
|
+
// counts where Ajv takes a branch, and a branch may read a value.
|
|
7042
|
+
...await locationFindings(label, own, view.data, schemaSet, members, merged, extracted.lineFor)
|
|
7043
|
+
];
|
|
7044
|
+
} catch (err) {
|
|
7045
|
+
if (!(err instanceof DocmetaError) || resolved.source !== "document") {
|
|
7046
|
+
throw err;
|
|
7047
|
+
}
|
|
7048
|
+
results.push(
|
|
7049
|
+
parseErrorResult(label, extractor.name, err.message, "schema")
|
|
7050
|
+
);
|
|
7051
|
+
return;
|
|
7052
|
+
}
|
|
7053
|
+
for (const { key, file, collection } of merged.collisions) {
|
|
7054
|
+
const line = extracted.lineFor(key);
|
|
7055
|
+
errors.push({
|
|
7056
|
+
schema: EXTERNAL_OWNED_SCHEMA,
|
|
7057
|
+
keyword: EXTERNAL_KEYWORD,
|
|
7058
|
+
subject: key,
|
|
7059
|
+
instancePath: externalMetadataPointer(key),
|
|
7060
|
+
message: `"${key}" is owned by manifest ${file} (collection ${collection}); remove it from the document`,
|
|
7061
|
+
...line != null ? { line } : {}
|
|
7062
|
+
});
|
|
7063
|
+
}
|
|
7064
|
+
results.push({
|
|
7065
|
+
file: label,
|
|
7066
|
+
format: extractor.name,
|
|
7067
|
+
ok: !errors.some(isErrorSeverity),
|
|
7068
|
+
schemas: schemaSet,
|
|
7069
|
+
errors
|
|
7070
|
+
});
|
|
7071
|
+
};
|
|
7072
|
+
if (usingStdin) {
|
|
7073
|
+
const content = opts.stdinContent ?? "";
|
|
7074
|
+
if (!forcedExtractor) {
|
|
7075
|
+
throw new DocmetaError(
|
|
7076
|
+
STDIN_REQUIRES_AS
|
|
7077
|
+
);
|
|
7078
|
+
}
|
|
7079
|
+
await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
|
|
7080
|
+
}
|
|
7081
|
+
for (const file of files) {
|
|
7082
|
+
const content = await readFile6(resolve7(base, file), "utf8");
|
|
7083
|
+
await processOne(file, content, extname3(file));
|
|
7084
|
+
}
|
|
7085
|
+
if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
|
|
7086
|
+
if (externalMetadata) {
|
|
7087
|
+
const byLabel = new Map(results.map((r) => [r.file, r]));
|
|
7088
|
+
const matched = /* @__PURE__ */ new Map();
|
|
7089
|
+
for (const [field, byValue] of joinHits) {
|
|
7090
|
+
matched.set(field, new Set(byValue.keys()));
|
|
7091
|
+
for (const hits of byValue.values()) {
|
|
7092
|
+
if (hits.length < 2) continue;
|
|
7093
|
+
for (const hit of hits) {
|
|
7094
|
+
const others = hits.filter((h) => h.label !== hit.label).map((h) => h.label).join(", ");
|
|
7095
|
+
const result = byLabel.get(hit.label);
|
|
7096
|
+
if (!result) continue;
|
|
7097
|
+
result.errors.push({
|
|
7098
|
+
schema: EXTERNAL_DUPLICATE_SCHEMA,
|
|
7099
|
+
keyword: EXTERNAL_KEYWORD,
|
|
7100
|
+
subject: hit.shown,
|
|
7101
|
+
instancePath: externalMetadataPointer(field),
|
|
7102
|
+
message: `${hits.length} documents carry ${field} "${hit.shown}"; ${hit.file} cannot tell them apart (${others})`,
|
|
7103
|
+
...hit.line != null ? { line: hit.line } : {}
|
|
7104
|
+
});
|
|
7105
|
+
result.ok = false;
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
if (!scopedByFlags) {
|
|
7110
|
+
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
7111
|
+
const orphans = orphanJoins(externalMetadata, matched, covered);
|
|
7112
|
+
if (orphans.length > 0) throw orphanError(orphans);
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
if (configuredChecks.length > 0 && opts.checks !== false) {
|
|
7116
|
+
if (scoped) {
|
|
7117
|
+
opts.onNotice?.(
|
|
7118
|
+
scopedToCollections ? `corpus checks skipped: run is scoped to collections ${collections.map((c) => c.name).join(", ")}` : "corpus checks skipped: run is scoped"
|
|
7119
|
+
);
|
|
7120
|
+
} else {
|
|
7121
|
+
const findings = await runChecks(configuredChecks, checkEntries, {
|
|
7122
|
+
// Every declared collection is a view (0041 rule 7), holding the
|
|
7123
|
+
// members this run loaded — decided by the same path arithmetic
|
|
7124
|
+
// `membersFor` uses, so a check and a `manni meta query` over the same
|
|
7125
|
+
// corpus see the same collections.
|
|
7126
|
+
collections: declaredCollections,
|
|
7127
|
+
...configDir !== void 0 ? { configDir } : {},
|
|
7128
|
+
base,
|
|
7129
|
+
// A check that names the `derived` table (0040) gets the same view
|
|
7130
|
+
// `query` builds, from the run's one derivation — which already
|
|
7131
|
+
// covers every field the checks can read, so the list handed back
|
|
7132
|
+
// here is not needed. Built only when asked for, because building
|
|
7133
|
+
// it spawns git.
|
|
7134
|
+
derive: async () => (await derivedOnce()).records,
|
|
7135
|
+
// Its columns include the configured commands' keys (0042), which a
|
|
7136
|
+
// check's context cannot read from a config since 0041.
|
|
7137
|
+
...deriveCommands !== void 0 ? { commands: deriveCommands } : {}
|
|
7138
|
+
});
|
|
7139
|
+
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7140
|
+
for (const [file, errs] of findings) {
|
|
7141
|
+
const result = byFile.get(file);
|
|
7142
|
+
if (!result && skippedDefaultOnly.has(file)) continue;
|
|
7143
|
+
if (!result) {
|
|
7144
|
+
throw new DocmetaError(
|
|
7145
|
+
`check findings for "${file}" have no validation result to attach to.`
|
|
7146
|
+
);
|
|
7147
|
+
}
|
|
7148
|
+
result.errors.push(...errs);
|
|
7149
|
+
result.ok = result.ok && !errs.some(isErrorSeverity);
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
if (deriveWillRun && deriveInputs.length > 0) {
|
|
7154
|
+
const derived = await derivedOnce();
|
|
7155
|
+
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7156
|
+
for (const input of deriveInputs) {
|
|
7157
|
+
const record = derived.records.get(input.label);
|
|
7158
|
+
const result = byFile.get(input.label);
|
|
7159
|
+
if (!record || !result) continue;
|
|
7160
|
+
const managed = managedViews.get(input.label);
|
|
7161
|
+
const managedHere = !deriveCovers(deriveConfig, membersFor(input.label)) ? [] : defaultedFields ? pageManaged.get(input.label) ?? [] : configuredFields;
|
|
7162
|
+
const fields = managedHere.filter(
|
|
7163
|
+
(field) => !(managed?.hidden.some((p) => isAtOrUnder(p, pointerOf(field))) ?? false)
|
|
7164
|
+
);
|
|
7165
|
+
let marked = [];
|
|
7166
|
+
if (managed !== void 0) {
|
|
7167
|
+
const stamped = { ...managed.data };
|
|
7168
|
+
for (const field of fields) {
|
|
7169
|
+
const d = record.fields[field];
|
|
7170
|
+
if (d != null) stamped[field] = d.value;
|
|
7171
|
+
}
|
|
7172
|
+
marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
|
|
7173
|
+
}
|
|
7174
|
+
const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
|
|
7175
|
+
const findings = fields.flatMap((field) => {
|
|
7176
|
+
if (field === PROVENANCE_FIELD) {
|
|
7177
|
+
const derivation = derived.provenance?.get(input.label);
|
|
7178
|
+
if (derivation === void 0) return [];
|
|
7179
|
+
return provenanceFindings(
|
|
7180
|
+
compareProvenance(provenanceEntries(asserted(field)), derivation),
|
|
7181
|
+
derivation.page.bodyLine
|
|
7182
|
+
);
|
|
7183
|
+
}
|
|
7184
|
+
const compared = compareDerived(field, asserted(field), record.fields[field]);
|
|
7185
|
+
const at2 = pointerOf(field);
|
|
7186
|
+
return staleFindings(
|
|
7187
|
+
[marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
|
|
7188
|
+
input.extracted.lineFor,
|
|
7189
|
+
managed?.locate
|
|
7190
|
+
);
|
|
7191
|
+
});
|
|
7192
|
+
if (findings.length === 0) continue;
|
|
7193
|
+
result.errors.push(...findings);
|
|
7194
|
+
result.ok = false;
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
const frame = { cwd, base: configDir ?? cwd, runBase: base };
|
|
7198
|
+
const { results: reported3, baseline } = await settleBaseline(
|
|
7199
|
+
results,
|
|
7200
|
+
resolveBaselineRequest(opts, config?.baseline, configDir, cwd, {
|
|
7201
|
+
current: DEFAULT_BASELINE_PATH,
|
|
7202
|
+
legacy: LEGACY_BASELINE_PATH
|
|
7203
|
+
}),
|
|
7204
|
+
frame
|
|
7205
|
+
);
|
|
7206
|
+
const failed = reported3.filter((r) => !r.ok).length;
|
|
7207
|
+
const count2 = (keep) => reported3.reduce((n, r) => n + r.errors.filter(keep).length, 0);
|
|
7208
|
+
const warnings = count2((e) => e.severity === "warning");
|
|
7209
|
+
const notices = count2((e) => e.severity === "notice");
|
|
7210
|
+
const summary = {
|
|
7211
|
+
files: reported3.length,
|
|
7212
|
+
passed: reported3.length - failed,
|
|
7213
|
+
failed,
|
|
7214
|
+
errors: count2(isErrorSeverity),
|
|
7215
|
+
// Omitted at zero, like `gitignoreSkipped` below: only the location
|
|
7216
|
+
// findings (proposal 0047) are warnings, so a run with no
|
|
7217
|
+
// `x-manni-location` marks keeps the summary as it was.
|
|
7218
|
+
...warnings > 0 ? { warnings } : {},
|
|
7219
|
+
...notices > 0 ? { notices } : {},
|
|
7220
|
+
// Omitted when nothing was skipped: there is nothing to audit, and the
|
|
7221
|
+
// JSON summary stays as it was for every run in a clean repo.
|
|
7222
|
+
...gitignoreSkipped > 0 ? { gitignoreSkipped } : {},
|
|
7223
|
+
...baseline ? { baseline } : {}
|
|
7224
|
+
};
|
|
7225
|
+
return { results: reported3, summary, frame };
|
|
7226
|
+
}
|
|
7227
|
+
|
|
4270
7228
|
export {
|
|
7229
|
+
collect,
|
|
7230
|
+
splitList,
|
|
7231
|
+
STDIN_REQUIRES_AS,
|
|
7232
|
+
readStdin,
|
|
7233
|
+
configOption,
|
|
7234
|
+
explicitFalse,
|
|
7235
|
+
reportConfig,
|
|
4271
7236
|
writeTextAtomic,
|
|
4272
7237
|
ENCRYPTION_KEY_ENV,
|
|
4273
7238
|
resolveEncryptionKey,
|
|
@@ -4287,7 +7252,6 @@ export {
|
|
|
4287
7252
|
META_CONTEXT,
|
|
4288
7253
|
EncryptionRefusal,
|
|
4289
7254
|
unreadableMessage,
|
|
4290
|
-
unverifiedWarning,
|
|
4291
7255
|
lazyKey,
|
|
4292
7256
|
pointerOf,
|
|
4293
7257
|
topLevelKeyOf,
|
|
@@ -4296,17 +7260,14 @@ export {
|
|
|
4296
7260
|
withValueAt,
|
|
4297
7261
|
encryptionView,
|
|
4298
7262
|
settleFindings,
|
|
4299
|
-
encryptionFindings,
|
|
4300
7263
|
redactForModel,
|
|
4301
7264
|
redactedDerived,
|
|
4302
7265
|
EXTERNAL_OWNED_SCHEMA,
|
|
4303
7266
|
EXTERNAL_DUPLICATE_SCHEMA,
|
|
4304
7267
|
EXTERNAL_KEYWORD,
|
|
4305
7268
|
PATH_JOIN,
|
|
4306
|
-
reportedPath,
|
|
4307
7269
|
externalMetadataJoin,
|
|
4308
7270
|
loadExternalMetadata,
|
|
4309
|
-
outsideRefusal,
|
|
4310
7271
|
mergeExternalMetadata,
|
|
4311
7272
|
mergeWithMarks,
|
|
4312
7273
|
externalMetadataPointer,
|
|
@@ -4317,7 +7278,6 @@ export {
|
|
|
4317
7278
|
LOCATION_KEYWORD,
|
|
4318
7279
|
LOCATION_EXTERNAL_SCHEMA,
|
|
4319
7280
|
LOCATION_PAGE_SCHEMA,
|
|
4320
|
-
LOCATION_FINDING_KEYWORD,
|
|
4321
7281
|
GRAPH_OUTPUT_KEYWORD,
|
|
4322
7282
|
compileWithFormats,
|
|
4323
7283
|
stringFormatNames,
|
|
@@ -4332,7 +7292,6 @@ export {
|
|
|
4332
7292
|
familyMarks,
|
|
4333
7293
|
SYSTEM_COLUMNS,
|
|
4334
7294
|
RESERVED,
|
|
4335
|
-
quoteIdent,
|
|
4336
7295
|
bindValue,
|
|
4337
7296
|
corpusDataColumns,
|
|
4338
7297
|
createDocsTable,
|
|
@@ -4380,19 +7339,57 @@ export {
|
|
|
4380
7339
|
toFileLines,
|
|
4381
7340
|
toBodyLines,
|
|
4382
7341
|
splitPageArgument,
|
|
4383
|
-
ZERO_SHA,
|
|
4384
|
-
DEFAULT_MACHINES,
|
|
4385
7342
|
provenanceEntries,
|
|
4386
7343
|
readProvenancePage,
|
|
4387
|
-
parseLinePorcelain,
|
|
4388
|
-
machineIdentity,
|
|
4389
|
-
deriveProvenance,
|
|
4390
7344
|
compareProvenance,
|
|
4391
7345
|
planProvenanceWrite,
|
|
4392
7346
|
rangeResults,
|
|
4393
7347
|
provenanceFindings,
|
|
4394
7348
|
renderProvenanceValue,
|
|
4395
7349
|
parseProvenanceTarget,
|
|
4396
|
-
|
|
7350
|
+
commandsOf,
|
|
7351
|
+
machinesOf,
|
|
7352
|
+
REVIEW_CACHE_DIR,
|
|
7353
|
+
REVIEW_CACHE_VERSION,
|
|
7354
|
+
ReviewCache,
|
|
7355
|
+
cachedClient,
|
|
7356
|
+
BinMissing,
|
|
7357
|
+
run,
|
|
7358
|
+
PATH_PLACEHOLDER,
|
|
7359
|
+
isPerFile,
|
|
7360
|
+
argvFor,
|
|
7361
|
+
valueOf,
|
|
7362
|
+
deriveFromCommands,
|
|
7363
|
+
parseOriginUrl,
|
|
7364
|
+
identityFromOrigin,
|
|
7365
|
+
GitHubClient,
|
|
7366
|
+
GitLabClient,
|
|
7367
|
+
createReviewClient,
|
|
7368
|
+
deriveFromReviews,
|
|
7369
|
+
mapConcurrent,
|
|
7370
|
+
provenanceFenced,
|
|
7371
|
+
FIELD_SOURCES,
|
|
7372
|
+
sourcesFor,
|
|
7373
|
+
consultedSources,
|
|
7374
|
+
assertSourcesAvailable,
|
|
7375
|
+
deriveMetadata,
|
|
7376
|
+
DERIVED_VIEW,
|
|
7377
|
+
DERIVED_ROWS,
|
|
7378
|
+
RESOLVED_VIEW,
|
|
7379
|
+
derivedColumns,
|
|
7380
|
+
mentionsDerived,
|
|
7381
|
+
fieldsForSql,
|
|
7382
|
+
createDerivedView,
|
|
7383
|
+
resolvedColumns,
|
|
7384
|
+
mentionsResolved,
|
|
7385
|
+
createResolvedView,
|
|
7386
|
+
deriveForTable,
|
|
7387
|
+
rowsToFindings,
|
|
7388
|
+
provenanceManifests,
|
|
7389
|
+
provenancePlace,
|
|
7390
|
+
readerManifests,
|
|
7391
|
+
readerPlace,
|
|
7392
|
+
stampManifests,
|
|
7393
|
+
runValidate
|
|
4397
7394
|
};
|
|
4398
|
-
//# sourceMappingURL=chunk-
|
|
7395
|
+
//# sourceMappingURL=chunk-5UB47BEV.js.map
|