@hawkeyexl/manni 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FL75S2L4.js → chunk-DOQUHXK3.js} +103 -5
- package/dist/chunk-DOQUHXK3.js.map +1 -0
- package/dist/{chunk-IRHWILCO.js → chunk-HYPDUKRU.js} +2965 -382
- package/dist/chunk-HYPDUKRU.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +719 -7
- package/dist/index.js +77 -1
- package/package.json +1 -1
- package/dist/chunk-FL75S2L4.js.map +0 -1
- package/dist/chunk-IRHWILCO.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hawkeyexl/manni",
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.1.1",
|
|
7
7
|
description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
|
|
8
8
|
type: "module",
|
|
9
9
|
bin: {
|
|
@@ -208,6 +208,11 @@ function parseCollections(raw, source, toError) {
|
|
|
208
208
|
`${source}: ${where}.name "${name2}" collides with the docs table every query reads. Pick another name, such as "site" or "pages".`
|
|
209
209
|
);
|
|
210
210
|
}
|
|
211
|
+
if (folded === "derived" || folded === "_derived_rows" || folded === "resolved") {
|
|
212
|
+
throw toError(
|
|
213
|
+
`${source}: ${where}.name "${name2}" collides with the derived table a query builds beside docs (the derived view, its _derived_rows table, and the resolved view over both). Pick another name.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
211
216
|
if (folded.startsWith("sqlite_")) {
|
|
212
217
|
throw toError(
|
|
213
218
|
`${source}: ${where}.name "${name2}" starts with "sqlite_", which SQLite reserves for its own objects. Pick another name.`
|
|
@@ -399,6 +404,7 @@ var ToolError = class extends Error {
|
|
|
399
404
|
function errorMessage(err) {
|
|
400
405
|
if (err instanceof Error) return err.message;
|
|
401
406
|
if (typeof err === "string") return err;
|
|
407
|
+
if (typeof err === "number" || typeof err === "bigint") return String(err);
|
|
402
408
|
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
403
409
|
return err.message;
|
|
404
410
|
}
|
|
@@ -437,8 +443,8 @@ var RENAME_ATTEMPTS = 3;
|
|
|
437
443
|
function errorCode(err) {
|
|
438
444
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
439
445
|
}
|
|
440
|
-
var wait = (ms) => new Promise((
|
|
441
|
-
setTimeout(
|
|
446
|
+
var wait = (ms) => new Promise((resolve17) => {
|
|
447
|
+
setTimeout(resolve17, ms);
|
|
442
448
|
});
|
|
443
449
|
async function writeFileAtomic(path, contents) {
|
|
444
450
|
const tmp = join(
|
|
@@ -632,11 +638,11 @@ import { resolve as resolvePath } from "node:path";
|
|
|
632
638
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
633
639
|
|
|
634
640
|
// src/meta/core/json-text.ts
|
|
635
|
-
function stripBom(
|
|
636
|
-
return
|
|
641
|
+
function stripBom(text2) {
|
|
642
|
+
return text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
637
643
|
}
|
|
638
|
-
function detectJsonIndent(
|
|
639
|
-
return /^\{\s*\n([ \t]+)/.exec(
|
|
644
|
+
function detectJsonIndent(text2) {
|
|
645
|
+
return /^\{\s*\n([ \t]+)/.exec(text2)?.[1] ?? " ";
|
|
640
646
|
}
|
|
641
647
|
function toJsonText(value) {
|
|
642
648
|
return JSON.stringify(value);
|
|
@@ -2905,6 +2911,11 @@ function assertPublishableBuiltinId(id) {
|
|
|
2905
2911
|
`Built-in id "${id}" is not publishable: the "external" first segment is reserved for external-metadata finding identities (external:owned, external:duplicate, proposal 0037).`
|
|
2906
2912
|
);
|
|
2907
2913
|
}
|
|
2914
|
+
if (first === "derived") {
|
|
2915
|
+
throw new Error(
|
|
2916
|
+
`Built-in id "${id}" is not publishable: the "derived" first segment is reserved for derived finding identities (derived:stale).`
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2908
2919
|
}
|
|
2909
2920
|
for (const id of BUILTINS.keys()) assertPublishableBuiltinId(id);
|
|
2910
2921
|
function listBuiltins() {
|
|
@@ -3032,8 +3043,8 @@ var SCHEMA_KEYS = [
|
|
|
3032
3043
|
"multipleOf"
|
|
3033
3044
|
];
|
|
3034
3045
|
var RETRY_DELAY_MS = 500;
|
|
3035
|
-
var wait2 = (ms) => new Promise((
|
|
3036
|
-
setTimeout(
|
|
3046
|
+
var wait2 = (ms) => new Promise((resolve17) => {
|
|
3047
|
+
setTimeout(resolve17, ms);
|
|
3037
3048
|
});
|
|
3038
3049
|
var RetryableFetchError = class extends DocmetaError {
|
|
3039
3050
|
};
|
|
@@ -3042,12 +3053,12 @@ function isAbort(err) {
|
|
|
3042
3053
|
return name2 === "TimeoutError" || name2 === "AbortError";
|
|
3043
3054
|
}
|
|
3044
3055
|
var JSON_PARSE_EXCERPT = /^Unexpected token '[\s\S]', (?:\.\.\.)?"([\s\S]*)"(?:\.\.\.)? is not valid JSON$/;
|
|
3045
|
-
function lineColumn(
|
|
3056
|
+
function lineColumn(text2, index) {
|
|
3046
3057
|
let line = 1;
|
|
3047
3058
|
let column = 1;
|
|
3048
|
-
const stop = Math.min(index,
|
|
3059
|
+
const stop = Math.min(index, text2.length);
|
|
3049
3060
|
for (let i = 0; i < stop; i++) {
|
|
3050
|
-
if (
|
|
3061
|
+
if (text2.charCodeAt(i) === 10) {
|
|
3051
3062
|
line += 1;
|
|
3052
3063
|
column = 1;
|
|
3053
3064
|
} else {
|
|
@@ -3056,12 +3067,12 @@ function lineColumn(text, index) {
|
|
|
3056
3067
|
}
|
|
3057
3068
|
return `line ${line} column ${column}`;
|
|
3058
3069
|
}
|
|
3059
|
-
function withoutFileExcerpt(message,
|
|
3070
|
+
function withoutFileExcerpt(message, text2) {
|
|
3060
3071
|
const match = JSON_PARSE_EXCERPT.exec(message);
|
|
3061
3072
|
if (!match) return message;
|
|
3062
3073
|
const quoted = match[1];
|
|
3063
|
-
const at = quoted === void 0 || quoted === "" ? -1 :
|
|
3064
|
-
return at < 0 ? "unexpected token" : `unexpected token at ${lineColumn(
|
|
3074
|
+
const at = quoted === void 0 || quoted === "" ? -1 : text2.indexOf(quoted);
|
|
3075
|
+
return at < 0 ? "unexpected token" : `unexpected token at ${lineColumn(text2, at)}`;
|
|
3065
3076
|
}
|
|
3066
3077
|
function excerpt(raw, limit = 200) {
|
|
3067
3078
|
const flat = raw.replace(/\s+/g, " ").trim();
|
|
@@ -3275,12 +3286,12 @@ async function loadSchema(ref, options = {}) {
|
|
|
3275
3286
|
if (pin?.integrity !== void 0) {
|
|
3276
3287
|
assertIntegrity(ref, bytes, pin, pin.integrity);
|
|
3277
3288
|
}
|
|
3278
|
-
const
|
|
3289
|
+
const text2 = stripBom(bytes.toString("utf8"));
|
|
3279
3290
|
try {
|
|
3280
|
-
return JSON.parse(
|
|
3291
|
+
return JSON.parse(text2);
|
|
3281
3292
|
} catch (err) {
|
|
3282
3293
|
throw new DocmetaError(
|
|
3283
|
-
`Schema file "${ref}" is not valid JSON: ${withoutFileExcerpt(errorMessage(err),
|
|
3294
|
+
`Schema file "${ref}" is not valid JSON: ${withoutFileExcerpt(errorMessage(err), text2)}`
|
|
3284
3295
|
);
|
|
3285
3296
|
}
|
|
3286
3297
|
}
|
|
@@ -4003,9 +4014,9 @@ function mergeToml(inner, patch, deletions = []) {
|
|
|
4003
4014
|
if (insertAt >= lines.length) out.push(...appended);
|
|
4004
4015
|
return out.join("\n");
|
|
4005
4016
|
}
|
|
4006
|
-
function parseJsonBlockText(
|
|
4007
|
-
if (
|
|
4008
|
-
const parsed2 = JSON.parse(
|
|
4017
|
+
function parseJsonBlockText(text2) {
|
|
4018
|
+
if (text2.trim() === "") return {};
|
|
4019
|
+
const parsed2 = JSON.parse(text2);
|
|
4009
4020
|
if (parsed2 == null || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
|
|
4010
4021
|
throw new DocmetaError(
|
|
4011
4022
|
"Cannot rewrite front matter: the root must be an object."
|
|
@@ -4013,14 +4024,14 @@ function parseJsonBlockText(text) {
|
|
|
4013
4024
|
}
|
|
4014
4025
|
return parsed2;
|
|
4015
4026
|
}
|
|
4016
|
-
function parseBlock(flavor,
|
|
4027
|
+
function parseBlock(flavor, text2) {
|
|
4017
4028
|
switch (flavor) {
|
|
4018
4029
|
case "json":
|
|
4019
|
-
return parseJsonBlockText(
|
|
4030
|
+
return parseJsonBlockText(text2);
|
|
4020
4031
|
case "toml":
|
|
4021
|
-
return
|
|
4032
|
+
return text2.trim() === "" ? {} : parseToml2(text2);
|
|
4022
4033
|
case "yaml": {
|
|
4023
|
-
const doc = parseDocument2(
|
|
4034
|
+
const doc = parseDocument2(text2);
|
|
4024
4035
|
if (doc.errors.length > 0) {
|
|
4025
4036
|
throw new DocmetaError(
|
|
4026
4037
|
`Cannot rewrite invalid YAML front matter: ${doc.errors[0]?.message ?? "parse error"}`
|
|
@@ -4160,10 +4171,10 @@ function parseTitle(lines) {
|
|
|
4160
4171
|
if (FIELD.test(first)) return { next: i };
|
|
4161
4172
|
const over = adornment(first);
|
|
4162
4173
|
if (over) {
|
|
4163
|
-
const
|
|
4174
|
+
const text2 = lines[i + 1] ?? "";
|
|
4164
4175
|
const under2 = adornment(lines[i + 2] ?? "");
|
|
4165
|
-
if (
|
|
4166
|
-
return { title:
|
|
4176
|
+
if (text2.trim() !== "" && !FIELD.test(text2) && under2 != null && under2.char === over.char && under2.length === over.length && over.length >= text2.trim().length) {
|
|
4177
|
+
return { title: text2.trim(), line: i + 2, next: i + 3 };
|
|
4167
4178
|
}
|
|
4168
4179
|
return { next: i };
|
|
4169
4180
|
}
|
|
@@ -4529,13 +4540,13 @@ function isMetadataAttribute(name2) {
|
|
|
4529
4540
|
return name2 !== "xmlns" && !name2.startsWith("xmlns:");
|
|
4530
4541
|
}
|
|
4531
4542
|
function elementText(el, allowEmpty = false) {
|
|
4532
|
-
let
|
|
4543
|
+
let text2 = "";
|
|
4533
4544
|
for (let n = el.firstChild; n; n = n.nextSibling) {
|
|
4534
4545
|
if (n.nodeType === 1) return void 0;
|
|
4535
|
-
if (n.nodeType === 3 || n.nodeType === 4)
|
|
4546
|
+
if (n.nodeType === 3 || n.nodeType === 4) text2 += n.nodeValue ?? "";
|
|
4536
4547
|
}
|
|
4537
|
-
if (!allowEmpty &&
|
|
4538
|
-
return
|
|
4548
|
+
if (!allowEmpty && text2.trim() === "") return void 0;
|
|
4549
|
+
return text2.trim();
|
|
4539
4550
|
}
|
|
4540
4551
|
function ditaLiftedElements(container) {
|
|
4541
4552
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -4572,15 +4583,15 @@ function own(table, key) {
|
|
|
4572
4583
|
function liftableChildren(root) {
|
|
4573
4584
|
const out = /* @__PURE__ */ new Map();
|
|
4574
4585
|
for (const child of childElements(root)) {
|
|
4575
|
-
const
|
|
4576
|
-
if (
|
|
4586
|
+
const text2 = elementText(child);
|
|
4587
|
+
if (text2 === void 0) continue;
|
|
4577
4588
|
const key = liftKey(root.nodeName, child.nodeName);
|
|
4578
4589
|
const group = out.get(key);
|
|
4579
4590
|
if (group) {
|
|
4580
4591
|
group.els.push(child);
|
|
4581
|
-
group.values.push(typeValue3(
|
|
4592
|
+
group.values.push(typeValue3(text2));
|
|
4582
4593
|
} else {
|
|
4583
|
-
out.set(key, { els: [child], values: [typeValue3(
|
|
4594
|
+
out.set(key, { els: [child], values: [typeValue3(text2)] });
|
|
4584
4595
|
}
|
|
4585
4596
|
}
|
|
4586
4597
|
return out;
|
|
@@ -4930,7 +4941,7 @@ function ownLift(container, element) {
|
|
|
4930
4941
|
const lifts = DITA_LIFTS[container];
|
|
4931
4942
|
return lifts && Object.prototype.hasOwnProperty.call(lifts, element) ? lifts[element] : void 0;
|
|
4932
4943
|
}
|
|
4933
|
-
function newElementEdits(content, starts, root, shape,
|
|
4944
|
+
function newElementEdits(content, starts, root, shape, items2, emit, escape) {
|
|
4934
4945
|
const eol = content.includes(CRLF) ? CRLF : LF;
|
|
4935
4946
|
const liftRootEl = liftRoot(root, shape);
|
|
4936
4947
|
const tagsFor = (item) => {
|
|
@@ -4944,7 +4955,7 @@ function newElementEdits(content, starts, root, shape, items, emit, escape) {
|
|
|
4944
4955
|
});
|
|
4945
4956
|
};
|
|
4946
4957
|
const byContainer = /* @__PURE__ */ new Map();
|
|
4947
|
-
for (const item of
|
|
4958
|
+
for (const item of items2) {
|
|
4948
4959
|
const group = byContainer.get(item.container);
|
|
4949
4960
|
if (group) group.push(item);
|
|
4950
4961
|
else byContainer.set(item.container, [item]);
|
|
@@ -5291,13 +5302,13 @@ function assertNoElementCollision(key, root) {
|
|
|
5291
5302
|
);
|
|
5292
5303
|
}
|
|
5293
5304
|
function emitScalar(key, value) {
|
|
5294
|
-
const
|
|
5295
|
-
if (
|
|
5305
|
+
const text2 = stringifyYaml(value).replace(/\n$/, "");
|
|
5306
|
+
if (text2.includes("\n")) {
|
|
5296
5307
|
throw new DocmetaError(
|
|
5297
5308
|
`Refusing to write "${key}": the value needs more than one line, which an XML attribute cannot hold. Set it manually.`
|
|
5298
5309
|
);
|
|
5299
5310
|
}
|
|
5300
|
-
return
|
|
5311
|
+
return text2;
|
|
5301
5312
|
}
|
|
5302
5313
|
function escapeAttr(value, quote) {
|
|
5303
5314
|
const escaped = value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -5368,13 +5379,13 @@ var HEAD_NOT_METADATA = /* @__PURE__ */ new Set(["script", "style"]);
|
|
|
5368
5379
|
var HEAD_SINGLETONS = /* @__PURE__ */ new Set(["title", "base"]);
|
|
5369
5380
|
var elementName = (el) => el ? el.tagName.toLowerCase() : "";
|
|
5370
5381
|
function headText(el, allowEmpty = false) {
|
|
5371
|
-
let
|
|
5382
|
+
let text2 = "";
|
|
5372
5383
|
for (const node of el.childNodes) {
|
|
5373
5384
|
if (defaultTreeAdapter.isElementNode(node)) return void 0;
|
|
5374
|
-
if (defaultTreeAdapter.isTextNode(node))
|
|
5385
|
+
if (defaultTreeAdapter.isTextNode(node)) text2 += node.value;
|
|
5375
5386
|
}
|
|
5376
|
-
if (!allowEmpty &&
|
|
5377
|
-
return
|
|
5387
|
+
if (!allowEmpty && text2.trim() === "") return void 0;
|
|
5388
|
+
return text2.trim();
|
|
5378
5389
|
}
|
|
5379
5390
|
function liftableHeadChildren(head) {
|
|
5380
5391
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -5382,15 +5393,15 @@ function liftableHeadChildren(head) {
|
|
|
5382
5393
|
if (!defaultTreeAdapter.isElementNode(node)) continue;
|
|
5383
5394
|
const name2 = node.tagName.toLowerCase();
|
|
5384
5395
|
if (HEAD_NOT_METADATA.has(name2)) continue;
|
|
5385
|
-
const
|
|
5386
|
-
if (
|
|
5396
|
+
const text2 = headText(node);
|
|
5397
|
+
if (text2 === void 0) continue;
|
|
5387
5398
|
const key = liftKey("head", name2);
|
|
5388
5399
|
const group = out.get(key);
|
|
5389
5400
|
if (group) {
|
|
5390
5401
|
group.els.push(node);
|
|
5391
|
-
group.texts.push(
|
|
5402
|
+
group.texts.push(text2);
|
|
5392
5403
|
} else {
|
|
5393
|
-
out.set(key, { els: [node], texts: [
|
|
5404
|
+
out.set(key, { els: [node], texts: [text2] });
|
|
5394
5405
|
}
|
|
5395
5406
|
}
|
|
5396
5407
|
return out;
|
|
@@ -5439,8 +5450,8 @@ function readHtml(source, options) {
|
|
|
5439
5450
|
} else if (node.tagName === "title") {
|
|
5440
5451
|
if (data.title === void 0) {
|
|
5441
5452
|
const first = node.childNodes[0];
|
|
5442
|
-
const
|
|
5443
|
-
setKey("title",
|
|
5453
|
+
const text2 = first && defaultTreeAdapter.isTextNode(first) ? first.value : "";
|
|
5454
|
+
setKey("title", text2, { kind: "title", el: node }, line, location?.startCol);
|
|
5444
5455
|
}
|
|
5445
5456
|
} else if (node.tagName === "meta") {
|
|
5446
5457
|
const key = attrValue(node, "name") ?? attrValue(node, "property");
|
|
@@ -5584,8 +5595,8 @@ function elementTextEdit2(el, key, emitted) {
|
|
|
5584
5595
|
}
|
|
5585
5596
|
function insertEdit2(content, head, at, tags) {
|
|
5586
5597
|
const { eol, indent } = headStyle(content, head, at);
|
|
5587
|
-
const
|
|
5588
|
-
return { start: at, end: at, text };
|
|
5598
|
+
const text2 = tags.map((t) => `${eol}${indent}${t}`).join("");
|
|
5599
|
+
return { start: at, end: at, text: text2 };
|
|
5589
5600
|
}
|
|
5590
5601
|
function headStyle(content, head, at) {
|
|
5591
5602
|
for (const child of head.childNodes) {
|
|
@@ -5621,13 +5632,13 @@ function attrValueSpan2(content, start, end) {
|
|
|
5621
5632
|
return { start: i, end, quote: DQ3, wrap: true };
|
|
5622
5633
|
}
|
|
5623
5634
|
function emitScalar2(key, value) {
|
|
5624
|
-
const
|
|
5625
|
-
if (
|
|
5635
|
+
const text2 = stringifyYaml2(value).replace(/\n$/, "");
|
|
5636
|
+
if (text2.includes("\n")) {
|
|
5626
5637
|
throw new DocmetaError(
|
|
5627
5638
|
`Refusing to write "${key}": the value needs more than one line, which an HTML attribute cannot hold. Set it manually.`
|
|
5628
5639
|
);
|
|
5629
5640
|
}
|
|
5630
|
-
return
|
|
5641
|
+
return text2;
|
|
5631
5642
|
}
|
|
5632
5643
|
function escapeAttr2(value, quote) {
|
|
5633
5644
|
const escaped = value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -5990,11 +6001,11 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
5990
6001
|
const byField = /* @__PURE__ */ new Map();
|
|
5991
6002
|
const entries = [];
|
|
5992
6003
|
for (const { collection, manifest } of configured) {
|
|
5993
|
-
let
|
|
6004
|
+
let text2;
|
|
5994
6005
|
let file;
|
|
5995
6006
|
if (classifyRef(manifest.file).kind === "url") {
|
|
5996
6007
|
file = manifest.file;
|
|
5997
|
-
|
|
6008
|
+
text2 = await fetchExternalMetadata(manifest.file, {
|
|
5998
6009
|
...manifest.tokenEnv !== void 0 ? { tokenEnv: manifest.tokenEnv } : {},
|
|
5999
6010
|
...opts.offline !== void 0 ? { offline: opts.offline } : {},
|
|
6000
6011
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
@@ -6003,32 +6014,32 @@ async function loadExternalMetadata(collections, opts) {
|
|
|
6003
6014
|
} else {
|
|
6004
6015
|
const abs = isAbsolute2(manifest.file) ? manifest.file : resolve5(opts.configDir, manifest.file);
|
|
6005
6016
|
file = reportedPath(abs, opts.base);
|
|
6006
|
-
|
|
6017
|
+
text2 = await readManifest(abs, file);
|
|
6007
6018
|
}
|
|
6008
6019
|
for (const key of manifest.keys) {
|
|
6009
6020
|
const list = owners.get(key);
|
|
6010
6021
|
if (list) list.push({ collection, file });
|
|
6011
6022
|
else owners.set(key, [{ collection, file }]);
|
|
6012
6023
|
}
|
|
6013
|
-
const
|
|
6024
|
+
const join8 = externalMetadataJoin(manifest);
|
|
6014
6025
|
let target;
|
|
6015
|
-
if (
|
|
6026
|
+
if (join8 === PATH_JOIN) {
|
|
6016
6027
|
target = byPath;
|
|
6017
6028
|
} else {
|
|
6018
|
-
const existing = byField.get(
|
|
6029
|
+
const existing = byField.get(join8);
|
|
6019
6030
|
if (existing) target = existing;
|
|
6020
6031
|
else {
|
|
6021
6032
|
target = /* @__PURE__ */ new Map();
|
|
6022
|
-
byField.set(
|
|
6033
|
+
byField.set(join8, target);
|
|
6023
6034
|
}
|
|
6024
6035
|
}
|
|
6025
6036
|
parseManifest(
|
|
6026
6037
|
manifest,
|
|
6027
6038
|
collection,
|
|
6028
|
-
|
|
6039
|
+
text2,
|
|
6029
6040
|
file,
|
|
6030
6041
|
opts.configDir,
|
|
6031
|
-
|
|
6042
|
+
join8,
|
|
6032
6043
|
target,
|
|
6033
6044
|
entries
|
|
6034
6045
|
);
|
|
@@ -6045,9 +6056,9 @@ async function readManifest(abs, file) {
|
|
|
6045
6056
|
);
|
|
6046
6057
|
}
|
|
6047
6058
|
}
|
|
6048
|
-
function parseManifest(manifest, collection,
|
|
6059
|
+
function parseManifest(manifest, collection, text2, file, configDir, join8, target, entries) {
|
|
6049
6060
|
const lc = new LineCounter2();
|
|
6050
|
-
const doc = parseDocument3(
|
|
6061
|
+
const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
|
|
6051
6062
|
const problem = doc.errors[0];
|
|
6052
6063
|
if (problem) {
|
|
6053
6064
|
throw new DocmetaError(
|
|
@@ -6058,7 +6069,7 @@ function parseManifest(manifest, collection, text, file, configDir, join7, targe
|
|
|
6058
6069
|
if (root === null || isScalar2(root) && root.value === null) return;
|
|
6059
6070
|
if (!isMap3(root)) {
|
|
6060
6071
|
throw new DocmetaError(
|
|
6061
|
-
`Manifest ${file}: the manifest must be a mapping from document ${
|
|
6072
|
+
`Manifest ${file}: the manifest must be a mapping from document ${join8 === PATH_JOIN ? "path" : `"${join8}"`} to owned keys.`
|
|
6062
6073
|
);
|
|
6063
6074
|
}
|
|
6064
6075
|
const owned = new Set(manifest.keys);
|
|
@@ -6083,7 +6094,7 @@ function parseManifest(manifest, collection, text, file, configDir, join7, targe
|
|
|
6083
6094
|
`Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
|
|
6084
6095
|
);
|
|
6085
6096
|
}
|
|
6086
|
-
const indexKey =
|
|
6097
|
+
const indexKey = join8 === PATH_JOIN ? resolve5(configDir, spelled) : spelled;
|
|
6087
6098
|
const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
|
|
6088
6099
|
for (const kv of pair.value.items) {
|
|
6089
6100
|
const key = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
|
|
@@ -6109,8 +6120,8 @@ function parseManifest(manifest, collection, text, file, configDir, join7, targe
|
|
|
6109
6120
|
target.set(indexKey, values);
|
|
6110
6121
|
entries.push({
|
|
6111
6122
|
collection,
|
|
6112
|
-
join:
|
|
6113
|
-
...
|
|
6123
|
+
join: join8,
|
|
6124
|
+
...join8 === PATH_JOIN ? { abs: indexKey } : {},
|
|
6114
6125
|
spelled,
|
|
6115
6126
|
file,
|
|
6116
6127
|
...entryLine === void 0 ? {} : { line: entryLine }
|
|
@@ -6225,6 +6236,97 @@ function orphanError(orphans) {
|
|
|
6225
6236
|
);
|
|
6226
6237
|
}
|
|
6227
6238
|
|
|
6239
|
+
// src/meta/core/derive/types.ts
|
|
6240
|
+
var DERIVABLE_FIELDS = [
|
|
6241
|
+
"created",
|
|
6242
|
+
"last-updated",
|
|
6243
|
+
"authors",
|
|
6244
|
+
"owner",
|
|
6245
|
+
"reviewed-by",
|
|
6246
|
+
"last-reviewed"
|
|
6247
|
+
];
|
|
6248
|
+
function isBuiltinField(x) {
|
|
6249
|
+
return DERIVABLE_FIELDS.includes(x);
|
|
6250
|
+
}
|
|
6251
|
+
var DERIVE_SOURCES = ["git", "codeowners", "github", "gitlab", "command"];
|
|
6252
|
+
function isDeriveSource(x) {
|
|
6253
|
+
return DERIVE_SOURCES.includes(x);
|
|
6254
|
+
}
|
|
6255
|
+
function derivableFields(commands) {
|
|
6256
|
+
return [...DERIVABLE_FIELDS, ...Object.keys(commands ?? {}).sort()];
|
|
6257
|
+
}
|
|
6258
|
+
var DERIVED_STALE_SCHEMA = "derived:stale";
|
|
6259
|
+
var DERIVED_KEYWORD = "derived";
|
|
6260
|
+
function compareDerived(field, asserted, derived) {
|
|
6261
|
+
const base = {
|
|
6262
|
+
field,
|
|
6263
|
+
...asserted !== void 0 ? { asserted } : {}
|
|
6264
|
+
};
|
|
6265
|
+
if (derived == null) {
|
|
6266
|
+
return { ...base, derived: null, status: "unknown", written: false };
|
|
6267
|
+
}
|
|
6268
|
+
const status = asserted === void 0 ? "unset" : deepEqual2(asserted, derived.value) ? "current" : "stale";
|
|
6269
|
+
return {
|
|
6270
|
+
...base,
|
|
6271
|
+
derived: derived.value,
|
|
6272
|
+
source: derived.source,
|
|
6273
|
+
evidence: derived.evidence,
|
|
6274
|
+
status,
|
|
6275
|
+
written: false
|
|
6276
|
+
};
|
|
6277
|
+
}
|
|
6278
|
+
function staleFindings(fields, lineFor) {
|
|
6279
|
+
const findings = [];
|
|
6280
|
+
for (const f of fields) {
|
|
6281
|
+
if (f.status !== "stale" && f.status !== "unset") continue;
|
|
6282
|
+
const instancePath = `/${escapePointerSegment(f.field)}`;
|
|
6283
|
+
const line = lineFor(instancePath);
|
|
6284
|
+
const says = `${f.source ?? "a source"} says ${fmt(f.derived)}`;
|
|
6285
|
+
const trail = f.evidence === void 0 ? "" : ` (${f.evidence})`;
|
|
6286
|
+
const head = f.status === "stale" ? `${f.field} says ${fmt(f.asserted)}` : `${f.field} is not set`;
|
|
6287
|
+
findings.push({
|
|
6288
|
+
schema: DERIVED_STALE_SCHEMA,
|
|
6289
|
+
keyword: DERIVED_KEYWORD,
|
|
6290
|
+
subject: f.field,
|
|
6291
|
+
instancePath,
|
|
6292
|
+
message: `${head}; ${says}${trail} \u2014 run manni meta derive`,
|
|
6293
|
+
...line !== void 0 ? { line } : {}
|
|
6294
|
+
});
|
|
6295
|
+
}
|
|
6296
|
+
return findings;
|
|
6297
|
+
}
|
|
6298
|
+
function fmt(value) {
|
|
6299
|
+
if (typeof value === "string") return value;
|
|
6300
|
+
return toJsonText(value) ?? String(value);
|
|
6301
|
+
}
|
|
6302
|
+
function deepEqual2(a, b) {
|
|
6303
|
+
if (isList(a) || isList(b)) {
|
|
6304
|
+
if (!isList(a) || !isList(b) || a.length !== b.length) return false;
|
|
6305
|
+
const unused = [...b];
|
|
6306
|
+
for (const x of a) {
|
|
6307
|
+
const i = unused.findIndex((y) => deepEqual2(x, y));
|
|
6308
|
+
if (i === -1) return false;
|
|
6309
|
+
unused.splice(i, 1);
|
|
6310
|
+
}
|
|
6311
|
+
return true;
|
|
6312
|
+
}
|
|
6313
|
+
if (isPlainObject(a) || isPlainObject(b)) {
|
|
6314
|
+
if (!isPlainObject(a) || !isPlainObject(b)) return false;
|
|
6315
|
+
const keys = Object.keys(a);
|
|
6316
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
6317
|
+
return keys.every(
|
|
6318
|
+
(k) => Object.hasOwn(b, k) && deepEqual2(a[k], b[k])
|
|
6319
|
+
);
|
|
6320
|
+
}
|
|
6321
|
+
return toJsonText(a) === toJsonText(b);
|
|
6322
|
+
}
|
|
6323
|
+
function isList(v) {
|
|
6324
|
+
return Array.isArray(v);
|
|
6325
|
+
}
|
|
6326
|
+
function isPlainObject(v) {
|
|
6327
|
+
return typeof v === "object" && v !== null && !isList(v) && !(v instanceof Date);
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6228
6330
|
// src/meta/core/config.ts
|
|
6229
6331
|
import { resolve as resolve7 } from "node:path";
|
|
6230
6332
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -6253,18 +6355,18 @@ var MOOSE_CONFIG_NAMES = [
|
|
|
6253
6355
|
"moose.config.yml"
|
|
6254
6356
|
];
|
|
6255
6357
|
async function readDocument(path, source, opts) {
|
|
6256
|
-
let
|
|
6358
|
+
let text2;
|
|
6257
6359
|
try {
|
|
6258
|
-
|
|
6360
|
+
text2 = await readFile4(path, "utf8");
|
|
6259
6361
|
} catch {
|
|
6260
6362
|
return null;
|
|
6261
6363
|
}
|
|
6262
|
-
return { text, doc: parseMapping(
|
|
6364
|
+
return { text: text2, doc: parseMapping(text2, source, opts) };
|
|
6263
6365
|
}
|
|
6264
|
-
function parseMapping(
|
|
6366
|
+
function parseMapping(text2, source, opts) {
|
|
6265
6367
|
let raw;
|
|
6266
6368
|
try {
|
|
6267
|
-
raw = parseYaml(
|
|
6369
|
+
raw = parseYaml(text2);
|
|
6268
6370
|
} catch (err) {
|
|
6269
6371
|
throw opts.toError(`${source}: invalid YAML: ${errorMessage(err)}`);
|
|
6270
6372
|
}
|
|
@@ -6385,8 +6487,29 @@ function asElementPaths(value, where, source) {
|
|
|
6385
6487
|
}
|
|
6386
6488
|
var OVERRIDE_KEYS = ["collection", "files", "schemas", "elements"];
|
|
6387
6489
|
var CHECK_KEYS = ["name", "query"];
|
|
6490
|
+
var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands"];
|
|
6491
|
+
var DERIVE_COMMAND_KEYS = ["run", "timeout"];
|
|
6492
|
+
var BUILTIN_CLAIMED_BY = {
|
|
6493
|
+
created: "git",
|
|
6494
|
+
"last-updated": "git",
|
|
6495
|
+
authors: "git",
|
|
6496
|
+
owner: "codeowners",
|
|
6497
|
+
"reviewed-by": "GitHub or GitLab",
|
|
6498
|
+
"last-reviewed": "GitHub or GitLab"
|
|
6499
|
+
};
|
|
6500
|
+
var DERIVED_TABLE_RESERVED = /* @__PURE__ */ new Set(["_path", "_sources"]);
|
|
6501
|
+
var DERIVABLE_LIST = `Derivable fields: ${DERIVABLE_FIELDS.join(", ")}, or any key with an entry in derive.commands.`;
|
|
6388
6502
|
var CHECK_NAME = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6389
|
-
var RESERVED_CHECK_NAMES = /* @__PURE__ */ new
|
|
6503
|
+
var RESERVED_CHECK_NAMES = /* @__PURE__ */ new Map([
|
|
6504
|
+
[
|
|
6505
|
+
"query",
|
|
6506
|
+
"`manni meta query --check` files its ad-hoc findings as check:query, and a configured check with the same name would share their identity"
|
|
6507
|
+
],
|
|
6508
|
+
[
|
|
6509
|
+
"derive",
|
|
6510
|
+
"`manni meta derive` is a verb of its own, and a check reporting as check:derive would read as its findings"
|
|
6511
|
+
]
|
|
6512
|
+
]);
|
|
6390
6513
|
var FILL_KEYS = [
|
|
6391
6514
|
"provider",
|
|
6392
6515
|
"model",
|
|
@@ -6399,6 +6522,7 @@ var CONFIG_KEYS = [
|
|
|
6399
6522
|
"schemas",
|
|
6400
6523
|
"overrides",
|
|
6401
6524
|
"checks",
|
|
6525
|
+
"derive",
|
|
6402
6526
|
"baseline",
|
|
6403
6527
|
"allowEmpty",
|
|
6404
6528
|
"respectGitignore",
|
|
@@ -6509,10 +6633,10 @@ function parseSchemaEntry(entry, where, source) {
|
|
|
6509
6633
|
}
|
|
6510
6634
|
return parsed2;
|
|
6511
6635
|
}
|
|
6512
|
-
function parseConfig(
|
|
6636
|
+
function parseConfig(text2, source) {
|
|
6513
6637
|
let raw;
|
|
6514
6638
|
try {
|
|
6515
|
-
raw = parseYaml2(
|
|
6639
|
+
raw = parseYaml2(text2);
|
|
6516
6640
|
} catch (err) {
|
|
6517
6641
|
throw new DocmetaError(
|
|
6518
6642
|
`${source}: invalid YAML: ${errorMessage(err)}`
|
|
@@ -6594,6 +6718,154 @@ function assertOverrideCollections(config, collections, source, section) {
|
|
|
6594
6718
|
);
|
|
6595
6719
|
}
|
|
6596
6720
|
}
|
|
6721
|
+
function manifestOwning(key, collections) {
|
|
6722
|
+
for (const [c, collection] of collections.entries()) {
|
|
6723
|
+
for (const [e, manifest] of collection.externalMetadata.entries()) {
|
|
6724
|
+
if (manifest.keys.includes(key)) {
|
|
6725
|
+
return { collection: c, name: collection.name, entry: e, file: manifest.file };
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
return void 0;
|
|
6730
|
+
}
|
|
6731
|
+
function assertManagedFieldsUnowned(config, collections, source, section) {
|
|
6732
|
+
const refuse = (path, owner) => {
|
|
6733
|
+
const message = `${source}: ${path} is owned by collections[${owner.collection}].externalMetadata[${owner.entry}] (${owner.file}) \u2014 a managed field has one authority, and a manifest key already has one. Drop it from one side.`;
|
|
6734
|
+
throw new DocmetaError(
|
|
6735
|
+
inSection(message, source, section)
|
|
6736
|
+
);
|
|
6737
|
+
};
|
|
6738
|
+
for (const key of Object.keys(config.derive?.commands ?? {})) {
|
|
6739
|
+
const owner = manifestOwning(key, collections);
|
|
6740
|
+
if (owner !== void 0) refuse(`derive.commands.${key}`, owner);
|
|
6741
|
+
}
|
|
6742
|
+
for (const [i, field] of (config.derive?.fields ?? []).entries()) {
|
|
6743
|
+
const owner = manifestOwning(field, collections);
|
|
6744
|
+
if (owner !== void 0) refuse(`derive.fields[${i}] "${field}"`, owner);
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
function parseDerive(raw, source) {
|
|
6748
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
6749
|
+
throw new DocmetaError(
|
|
6750
|
+
`${source}: "derive" must be a mapping. Write it as \`derive:\` with \`fields:\` beneath it.`
|
|
6751
|
+
);
|
|
6752
|
+
}
|
|
6753
|
+
const e = raw;
|
|
6754
|
+
rejectUnknownKeys2(e, DERIVE_KEYS, '"derive"', source);
|
|
6755
|
+
if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0) {
|
|
6756
|
+
throw new DocmetaError(
|
|
6757
|
+
`${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\` or \`codeowners:\` to shape the reads.`
|
|
6758
|
+
);
|
|
6759
|
+
}
|
|
6760
|
+
const commands = e.commands === void 0 ? void 0 : parseDeriveCommands(e.commands, source);
|
|
6761
|
+
if (e.fields !== void 0 && (!Array.isArray(e.fields) || e.fields.length === 0 || e.fields.some((f) => typeof f !== "string"))) {
|
|
6762
|
+
throw new DocmetaError(
|
|
6763
|
+
`${source}: derive.fields must be a non-empty list of field names. ${DERIVABLE_LIST}`
|
|
6764
|
+
);
|
|
6765
|
+
}
|
|
6766
|
+
const fields = [];
|
|
6767
|
+
(e.fields ?? []).forEach((f, i) => {
|
|
6768
|
+
if (!isBuiltinField(f) && !(commands !== void 0 && Object.hasOwn(commands, f))) {
|
|
6769
|
+
throw new DocmetaError(
|
|
6770
|
+
`${source}: derive.fields[${i}] "${f}" is not derivable. ${DERIVABLE_LIST}`
|
|
6771
|
+
);
|
|
6772
|
+
}
|
|
6773
|
+
if (fields.includes(f)) {
|
|
6774
|
+
throw new DocmetaError(`${source}: derive.fields lists "${f}" twice.`);
|
|
6775
|
+
}
|
|
6776
|
+
fields.push(f);
|
|
6777
|
+
});
|
|
6778
|
+
const derive = { fields };
|
|
6779
|
+
if (e.sources !== void 0) {
|
|
6780
|
+
if (!Array.isArray(e.sources) || e.sources.length === 0 || e.sources.some((s) => typeof s !== "string")) {
|
|
6781
|
+
throw new DocmetaError(
|
|
6782
|
+
`${source}: derive.sources must be a non-empty list of source names. Sources: ${DERIVE_SOURCES.join(", ")}.`
|
|
6783
|
+
);
|
|
6784
|
+
}
|
|
6785
|
+
const sources = [];
|
|
6786
|
+
e.sources.forEach((s, i) => {
|
|
6787
|
+
if (!isDeriveSource(s)) {
|
|
6788
|
+
throw new DocmetaError(
|
|
6789
|
+
`${source}: derive.sources[${i}] "${s}" is not a source. Sources: ${DERIVE_SOURCES.join(", ")}.`
|
|
6790
|
+
);
|
|
6791
|
+
}
|
|
6792
|
+
if (sources.includes(s)) {
|
|
6793
|
+
throw new DocmetaError(`${source}: derive.sources lists "${s}" twice.`);
|
|
6794
|
+
}
|
|
6795
|
+
sources.push(s);
|
|
6796
|
+
});
|
|
6797
|
+
derive.sources = sources;
|
|
6798
|
+
}
|
|
6799
|
+
if (e.codeowners !== void 0) {
|
|
6800
|
+
if (typeof e.codeowners !== "string" || e.codeowners.trim() === "") {
|
|
6801
|
+
throw new DocmetaError(
|
|
6802
|
+
`${source}: derive.codeowners must be a non-empty path to a CODEOWNERS file, relative to the config file.`
|
|
6803
|
+
);
|
|
6804
|
+
}
|
|
6805
|
+
derive.codeowners = e.codeowners;
|
|
6806
|
+
}
|
|
6807
|
+
if (commands !== void 0) derive.commands = commands;
|
|
6808
|
+
return derive;
|
|
6809
|
+
}
|
|
6810
|
+
function parseDeriveCommands(raw, source) {
|
|
6811
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
6812
|
+
throw new DocmetaError(
|
|
6813
|
+
`${source}: derive.commands must be a mapping of field name to command.`
|
|
6814
|
+
);
|
|
6815
|
+
}
|
|
6816
|
+
const commands = {};
|
|
6817
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
6818
|
+
if (key.trim() === "") {
|
|
6819
|
+
throw new DocmetaError(`${source}: derive.commands has an empty field name.`);
|
|
6820
|
+
}
|
|
6821
|
+
if (key === FILE_SCHEMA_KEY) {
|
|
6822
|
+
throw new DocmetaError(
|
|
6823
|
+
`${source}: derive.commands may not derive "${FILE_SCHEMA_KEY}" \u2014 a command never chooses the schema a document is judged by.`
|
|
6824
|
+
);
|
|
6825
|
+
}
|
|
6826
|
+
if (isBuiltinField(key)) {
|
|
6827
|
+
throw new DocmetaError(
|
|
6828
|
+
`${source}: derive.commands.${key} targets a field ${BUILTIN_CLAIMED_BY[key]} already derives; a command may only derive a field no built-in source claims.`
|
|
6829
|
+
);
|
|
6830
|
+
}
|
|
6831
|
+
if (DERIVED_TABLE_RESERVED.has(key)) {
|
|
6832
|
+
throw new DocmetaError(
|
|
6833
|
+
`${source}: derive.commands.${key} collides with a column of the derived table a query builds (${[...DERIVED_TABLE_RESERVED].join(", ")}). Pick another field name.`
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
const where = `derive.commands.${key}`;
|
|
6837
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
6838
|
+
throw new DocmetaError(
|
|
6839
|
+
`${source}: ${where} must be a mapping with \`run:\` beneath it.`
|
|
6840
|
+
);
|
|
6841
|
+
}
|
|
6842
|
+
const c = value;
|
|
6843
|
+
rejectUnknownKeys2(c, DERIVE_COMMAND_KEYS, where, source);
|
|
6844
|
+
if (!Array.isArray(c.run) || c.run.length === 0) {
|
|
6845
|
+
throw new DocmetaError(
|
|
6846
|
+
`${source}: ${where}.run must be a non-empty list of strings, the program first.`
|
|
6847
|
+
);
|
|
6848
|
+
}
|
|
6849
|
+
const run2 = [];
|
|
6850
|
+
c.run.forEach((arg, j) => {
|
|
6851
|
+
if (typeof arg !== "string" || arg.trim() === "") {
|
|
6852
|
+
throw new DocmetaError(`${source}: ${where}.run[${j}] must be a non-empty string.`);
|
|
6853
|
+
}
|
|
6854
|
+
run2.push(arg);
|
|
6855
|
+
});
|
|
6856
|
+
const command = { run: run2 };
|
|
6857
|
+
if (c.timeout !== void 0) {
|
|
6858
|
+
if (typeof c.timeout !== "number" || !Number.isInteger(c.timeout) || c.timeout <= 0) {
|
|
6859
|
+
throw new DocmetaError(
|
|
6860
|
+
`${source}: ${where}.timeout must be a whole number of seconds, greater than zero.`
|
|
6861
|
+
);
|
|
6862
|
+
}
|
|
6863
|
+
command.timeout = c.timeout;
|
|
6864
|
+
}
|
|
6865
|
+
commands[key] = command;
|
|
6866
|
+
}
|
|
6867
|
+
return commands;
|
|
6868
|
+
}
|
|
6597
6869
|
function parseConfigDocument(raw, source) {
|
|
6598
6870
|
if (raw == null) return {};
|
|
6599
6871
|
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -6649,6 +6921,9 @@ function parseConfigDocument(raw, source) {
|
|
|
6649
6921
|
if (obj.checks !== void 0) {
|
|
6650
6922
|
config.checks = parseChecks(obj.checks, source);
|
|
6651
6923
|
}
|
|
6924
|
+
if (obj.derive !== void 0) {
|
|
6925
|
+
config.derive = parseDerive(obj.derive, source);
|
|
6926
|
+
}
|
|
6652
6927
|
if (obj.elements !== void 0) {
|
|
6653
6928
|
config.elements = asElementPaths(obj.elements, "elements", source);
|
|
6654
6929
|
}
|
|
@@ -6710,9 +6985,10 @@ function parseChecks(value, source) {
|
|
|
6710
6985
|
`${source}: checks[${i}].name "${e.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>).`
|
|
6711
6986
|
);
|
|
6712
6987
|
}
|
|
6713
|
-
|
|
6988
|
+
const reserved = RESERVED_CHECK_NAMES.get(e.name);
|
|
6989
|
+
if (reserved !== void 0) {
|
|
6714
6990
|
throw new DocmetaError(
|
|
6715
|
-
`${source}: checks[${i}].name "${e.name}" is reserved \u2014
|
|
6991
|
+
`${source}: checks[${i}].name "${e.name}" is reserved \u2014 ${reserved}. Pick another name.`
|
|
6716
6992
|
);
|
|
6717
6993
|
}
|
|
6718
6994
|
if (seen.has(e.name)) {
|
|
@@ -6815,8 +7091,8 @@ function parseFill(value, source) {
|
|
|
6815
7091
|
return fill;
|
|
6816
7092
|
}
|
|
6817
7093
|
function schemaTrustRoot(cwd, configDir) {
|
|
6818
|
-
const
|
|
6819
|
-
if (
|
|
7094
|
+
const git2 = findGitRoot(cwd);
|
|
7095
|
+
if (git2 !== null) return { dir: git2, source: "git" };
|
|
6820
7096
|
if (configDir !== void 0) return { dir: resolve7(configDir), source: "config" };
|
|
6821
7097
|
return { dir: resolve7(cwd), source: "cwd" };
|
|
6822
7098
|
}
|
|
@@ -6826,6 +7102,7 @@ async function loadConfig(explicitPath, cwd = process.cwd()) {
|
|
|
6826
7102
|
const section = file.wrapped ? META_SECTION : void 0;
|
|
6827
7103
|
const config = parseConfigValue(file.value, file.source, section);
|
|
6828
7104
|
assertOverrideCollections(config, file.collections, file.source, section);
|
|
7105
|
+
assertManagedFieldsUnowned(config, file.collections, file.source, section);
|
|
6829
7106
|
return {
|
|
6830
7107
|
config,
|
|
6831
7108
|
path: file.path,
|
|
@@ -7010,7 +7287,7 @@ function isParamName(name2) {
|
|
|
7010
7287
|
return PARAM_NAME_HEAD.exec(name2)?.[0] === name2;
|
|
7011
7288
|
}
|
|
7012
7289
|
function collectNamedParameters(sql) {
|
|
7013
|
-
const
|
|
7290
|
+
const tokens2 = [];
|
|
7014
7291
|
const seen = /* @__PURE__ */ new Set();
|
|
7015
7292
|
const prefixOf = /* @__PURE__ */ new Map();
|
|
7016
7293
|
let i = 0;
|
|
@@ -7040,14 +7317,14 @@ function collectNamedParameters(sql) {
|
|
|
7040
7317
|
prefixOf.set(m[0], ch);
|
|
7041
7318
|
if (!seen.has(token)) {
|
|
7042
7319
|
seen.add(token);
|
|
7043
|
-
|
|
7320
|
+
tokens2.push(token);
|
|
7044
7321
|
}
|
|
7045
7322
|
i += 1 + m[0].length;
|
|
7046
7323
|
} else {
|
|
7047
7324
|
i++;
|
|
7048
7325
|
}
|
|
7049
7326
|
}
|
|
7050
|
-
return
|
|
7327
|
+
return tokens2;
|
|
7051
7328
|
}
|
|
7052
7329
|
function stripLeadingTrivia(sql) {
|
|
7053
7330
|
let i = 0;
|
|
@@ -7060,16 +7337,16 @@ function stripLeadingTrivia(sql) {
|
|
|
7060
7337
|
return sql.slice(i);
|
|
7061
7338
|
}
|
|
7062
7339
|
}
|
|
7063
|
-
function isTrivia(
|
|
7340
|
+
function isTrivia(text2) {
|
|
7064
7341
|
let i = 0;
|
|
7065
|
-
while (i <
|
|
7066
|
-
const ch =
|
|
7342
|
+
while (i < text2.length) {
|
|
7343
|
+
const ch = text2[i];
|
|
7067
7344
|
if (ch === void 0 || /\s/.test(ch) || ch === ";") {
|
|
7068
7345
|
i++;
|
|
7069
7346
|
continue;
|
|
7070
7347
|
}
|
|
7071
|
-
if (startsComment(
|
|
7072
|
-
i = skipComment(
|
|
7348
|
+
if (startsComment(text2, i)) {
|
|
7349
|
+
i = skipComment(text2, i);
|
|
7073
7350
|
continue;
|
|
7074
7351
|
}
|
|
7075
7352
|
return false;
|
|
@@ -7081,8 +7358,8 @@ function loadSqlite() {
|
|
|
7081
7358
|
sqliteModule ??= (async () => {
|
|
7082
7359
|
const original = process.emitWarning.bind(process);
|
|
7083
7360
|
const filtered = (warning, ...rest) => {
|
|
7084
|
-
const
|
|
7085
|
-
if (
|
|
7361
|
+
const text2 = typeof warning === "string" ? warning : warning.message;
|
|
7362
|
+
if (text2.includes("SQLite is an experimental feature")) return;
|
|
7086
7363
|
Reflect.apply(original, process, [warning, ...rest]);
|
|
7087
7364
|
};
|
|
7088
7365
|
process.emitWarning = filtered;
|
|
@@ -7358,119 +7635,1751 @@ function toFieldError(schema, e, lineFor, colFor, locate) {
|
|
|
7358
7635
|
};
|
|
7359
7636
|
}
|
|
7360
7637
|
|
|
7361
|
-
// src/meta/
|
|
7362
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
7363
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
7364
|
-
import { resolve as resolve9, extname as extname2 } from "node:path";
|
|
7365
|
-
|
|
7366
|
-
// src/meta/core/baseline.ts
|
|
7638
|
+
// src/meta/core/derive/cache.ts
|
|
7367
7639
|
import { createHash as createHash3 } from "node:crypto";
|
|
7368
|
-
import {
|
|
7369
|
-
import {
|
|
7370
|
-
var
|
|
7371
|
-
var
|
|
7372
|
-
var
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7640
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
7641
|
+
import { join as join5 } from "node:path";
|
|
7642
|
+
var REVIEW_CACHE_DIR = ".manni/meta/review-cache";
|
|
7643
|
+
var REVIEW_CACHE_VERSION = 1;
|
|
7644
|
+
var ReviewCache = class {
|
|
7645
|
+
constructor(dir, enabled) {
|
|
7646
|
+
this.dir = dir;
|
|
7647
|
+
this.enabled = enabled;
|
|
7648
|
+
}
|
|
7649
|
+
dir;
|
|
7650
|
+
enabled;
|
|
7651
|
+
entryPath(key) {
|
|
7652
|
+
const digest = createHash3("sha256").update(key).digest("hex");
|
|
7653
|
+
return join5(this.dir, `${digest}.json`);
|
|
7654
|
+
}
|
|
7655
|
+
/** The cached change, or `undefined` for a miss. Never `null`: nulls are not stored. */
|
|
7656
|
+
get(key) {
|
|
7657
|
+
if (!this.enabled) return void 0;
|
|
7658
|
+
let raw;
|
|
7659
|
+
try {
|
|
7660
|
+
raw = JSON.parse(readFileSync(this.entryPath(key), "utf8"));
|
|
7661
|
+
} catch {
|
|
7662
|
+
return void 0;
|
|
7663
|
+
}
|
|
7664
|
+
if (!isRecord(raw)) return void 0;
|
|
7665
|
+
if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
|
|
7666
|
+
const change2 = readChange(raw.change);
|
|
7667
|
+
return change2 ?? void 0;
|
|
7668
|
+
}
|
|
7669
|
+
set(key, value) {
|
|
7670
|
+
if (!this.enabled) return;
|
|
7671
|
+
const entry = { version: REVIEW_CACHE_VERSION, key, change: value };
|
|
7672
|
+
const path = this.entryPath(key);
|
|
7673
|
+
const tmp = `${path}.${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
7674
|
+
try {
|
|
7675
|
+
mkdirSync(this.dir, { recursive: true });
|
|
7676
|
+
writeFileSync(tmp, `${JSON.stringify(entry, null, 2)}
|
|
7677
|
+
`, "utf8");
|
|
7678
|
+
renameSync(tmp, path);
|
|
7679
|
+
} catch {
|
|
7680
|
+
try {
|
|
7681
|
+
rmSync(tmp, { force: true });
|
|
7682
|
+
} catch {
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
};
|
|
7687
|
+
function cachedClient(client, cache, identity) {
|
|
7688
|
+
return {
|
|
7689
|
+
detect: () => client.detect(),
|
|
7690
|
+
status: () => client.status(),
|
|
7691
|
+
async mergedChangeFor(sha) {
|
|
7692
|
+
if (identity === null) return client.mergedChangeFor(sha);
|
|
7693
|
+
const key = `${identity.host}/${identity.project}/${sha}`;
|
|
7694
|
+
const hit = cache.get(key);
|
|
7695
|
+
if (hit !== void 0) return hit;
|
|
7696
|
+
const change2 = await client.mergedChangeFor(sha);
|
|
7697
|
+
if (change2 !== null) cache.set(key, change2);
|
|
7698
|
+
return change2;
|
|
7699
|
+
}
|
|
7700
|
+
};
|
|
7384
7701
|
}
|
|
7385
|
-
function
|
|
7386
|
-
|
|
7387
|
-
canonicalSchemaRef(e.schema, ctx),
|
|
7388
|
-
e.instancePath,
|
|
7389
|
-
e.keyword,
|
|
7390
|
-
e.subject ?? ""
|
|
7391
|
-
];
|
|
7392
|
-
return createHash3("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
7702
|
+
function isRecord(v) {
|
|
7703
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7393
7704
|
}
|
|
7394
|
-
function
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7705
|
+
function readChange(v) {
|
|
7706
|
+
if (!isRecord(v)) return null;
|
|
7707
|
+
const { id, mergedAt, approvals } = v;
|
|
7708
|
+
if (typeof id !== "number" || typeof mergedAt !== "string") return null;
|
|
7709
|
+
if (!Array.isArray(approvals)) return null;
|
|
7710
|
+
const read = [];
|
|
7711
|
+
for (const a of approvals) {
|
|
7712
|
+
if (!isRecord(a)) return null;
|
|
7713
|
+
if (typeof a.login !== "string" || typeof a.submittedAt !== "string") return null;
|
|
7714
|
+
read.push({ login: a.login, submittedAt: a.submittedAt });
|
|
7398
7715
|
}
|
|
7399
|
-
return
|
|
7400
|
-
{
|
|
7401
|
-
version: baseline.version,
|
|
7402
|
-
generatedWith: baseline.generatedWith,
|
|
7403
|
-
entries
|
|
7404
|
-
},
|
|
7405
|
-
null,
|
|
7406
|
-
2
|
|
7407
|
-
)}
|
|
7408
|
-
`;
|
|
7716
|
+
return { id, mergedAt, approvals: read };
|
|
7409
7717
|
}
|
|
7410
|
-
|
|
7411
|
-
|
|
7718
|
+
|
|
7719
|
+
// src/meta/core/derive/spawn.ts
|
|
7720
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7721
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
7722
|
+
var MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
7723
|
+
var BinMissing = class extends Error {
|
|
7724
|
+
};
|
|
7725
|
+
function run(bin, args, opts) {
|
|
7726
|
+
return new Promise((settle, reject) => {
|
|
7727
|
+
let done = false;
|
|
7728
|
+
const finish = (r) => {
|
|
7729
|
+
if (done) return;
|
|
7730
|
+
done = true;
|
|
7731
|
+
settle(r);
|
|
7732
|
+
};
|
|
7733
|
+
const fail = (err) => {
|
|
7734
|
+
if (done) return;
|
|
7735
|
+
done = true;
|
|
7736
|
+
reject(err);
|
|
7737
|
+
};
|
|
7738
|
+
let child;
|
|
7739
|
+
try {
|
|
7740
|
+
child = spawn2(bin, [...opts.prefixArgs ?? [], ...args], {
|
|
7741
|
+
cwd: opts.cwd,
|
|
7742
|
+
windowsHide: true
|
|
7743
|
+
});
|
|
7744
|
+
} catch (err) {
|
|
7745
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
7746
|
+
return;
|
|
7747
|
+
}
|
|
7748
|
+
let stdout = "";
|
|
7749
|
+
let stderr = "";
|
|
7750
|
+
let timedOut = false;
|
|
7751
|
+
let tooLarge = false;
|
|
7752
|
+
const stop = (r) => {
|
|
7753
|
+
clearTimeout(timer);
|
|
7754
|
+
child.kill();
|
|
7755
|
+
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
7756
|
+
finish({ code: null, ...r });
|
|
7757
|
+
};
|
|
7758
|
+
child.stdout.setEncoding("utf8");
|
|
7759
|
+
child.stdout.on("data", (chunk) => {
|
|
7760
|
+
stdout += chunk;
|
|
7761
|
+
if (stdout.length > MAX_OUTPUT_BYTES) {
|
|
7762
|
+
tooLarge = true;
|
|
7763
|
+
stop({ stdout, stderr, timedOut, tooLarge });
|
|
7764
|
+
}
|
|
7765
|
+
});
|
|
7766
|
+
child.stderr.setEncoding("utf8");
|
|
7767
|
+
child.stderr.on("data", (chunk) => {
|
|
7768
|
+
stderr += chunk;
|
|
7769
|
+
if (stderr.length > MAX_OUTPUT_BYTES) {
|
|
7770
|
+
tooLarge = true;
|
|
7771
|
+
stop({ stdout, stderr, timedOut, tooLarge });
|
|
7772
|
+
}
|
|
7773
|
+
});
|
|
7774
|
+
child.stdin.end();
|
|
7775
|
+
const timer = setTimeout(() => {
|
|
7776
|
+
timedOut = true;
|
|
7777
|
+
stop({ stdout, stderr, timedOut, tooLarge });
|
|
7778
|
+
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
|
|
7779
|
+
child.on("error", (err) => {
|
|
7780
|
+
clearTimeout(timer);
|
|
7781
|
+
fail(err.code === "ENOENT" ? new BinMissing(err.message) : err);
|
|
7782
|
+
});
|
|
7783
|
+
child.on("close", (code) => {
|
|
7784
|
+
clearTimeout(timer);
|
|
7785
|
+
finish({ code, stdout, stderr, timedOut, tooLarge });
|
|
7786
|
+
});
|
|
7787
|
+
});
|
|
7412
7788
|
}
|
|
7413
|
-
function
|
|
7414
|
-
|
|
7789
|
+
function commandLine(bin, args) {
|
|
7790
|
+
return [bin, ...args].join(" ");
|
|
7791
|
+
}
|
|
7792
|
+
function lastLine(text2) {
|
|
7793
|
+
const lines = text2.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
7794
|
+
return lines.at(-1) ?? "";
|
|
7795
|
+
}
|
|
7796
|
+
|
|
7797
|
+
// src/meta/core/derive/command.ts
|
|
7798
|
+
var PATH_PLACEHOLDER = "{path}";
|
|
7799
|
+
function isPerFile(command) {
|
|
7800
|
+
return command.run.some((arg) => arg.includes(PATH_PLACEHOLDER));
|
|
7801
|
+
}
|
|
7802
|
+
function argvFor(command, label) {
|
|
7803
|
+
return command.run.map((arg) => arg.split(PATH_PLACEHOLDER).join(label));
|
|
7804
|
+
}
|
|
7805
|
+
function valueOf(stdout) {
|
|
7806
|
+
const text2 = stdout.trim();
|
|
7807
|
+
if (text2 === "") return null;
|
|
7808
|
+
if (!/^[{["]/.test(text2)) return text2;
|
|
7415
7809
|
try {
|
|
7416
|
-
|
|
7417
|
-
} catch
|
|
7418
|
-
|
|
7419
|
-
}
|
|
7420
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
7421
|
-
bad(source, "top level must be an object.");
|
|
7422
|
-
}
|
|
7423
|
-
const obj = raw;
|
|
7424
|
-
if (obj.version !== BASELINE_VERSION) {
|
|
7425
|
-
bad(
|
|
7426
|
-
source,
|
|
7427
|
-
`unsupported version ${JSON.stringify(obj.version)} (this manni writes version ${BASELINE_VERSION}). Re-record it with \`manni meta validate --write-baseline\`.`
|
|
7428
|
-
);
|
|
7810
|
+
return JSON.parse(text2);
|
|
7811
|
+
} catch {
|
|
7812
|
+
return text2;
|
|
7429
7813
|
}
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7814
|
+
}
|
|
7815
|
+
async function deriveFromCommands(inputs, commands, opts) {
|
|
7816
|
+
const records = /* @__PURE__ */ new Map();
|
|
7817
|
+
const fields = Object.keys(commands);
|
|
7818
|
+
if (fields.length === 0 || inputs.length === 0) {
|
|
7819
|
+
return { status: { available: true }, records };
|
|
7434
7820
|
}
|
|
7435
|
-
const
|
|
7436
|
-
for (const
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
if (
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
);
|
|
7821
|
+
for (const input of inputs) records.set(input.label, {});
|
|
7822
|
+
for (const field of fields) {
|
|
7823
|
+
const command = commands[field];
|
|
7824
|
+
if (command === void 0) continue;
|
|
7825
|
+
if (isPerFile(command)) {
|
|
7826
|
+
for (const [label, record] of records) {
|
|
7827
|
+
const outcome = await runOne(field, argvFor(command, label), command.timeoutMs, opts);
|
|
7828
|
+
if ("reason" in outcome) return unavailable2(outcome.reason);
|
|
7829
|
+
record[field] = outcome.value;
|
|
7830
|
+
}
|
|
7831
|
+
} else {
|
|
7832
|
+
const outcome = await runOne(field, command.run, command.timeoutMs, opts);
|
|
7833
|
+
if ("reason" in outcome) return unavailable2(outcome.reason);
|
|
7834
|
+
for (const record of records.values()) record[field] = outcome.value;
|
|
7448
7835
|
}
|
|
7449
|
-
entries[file] = value;
|
|
7450
7836
|
}
|
|
7451
|
-
return {
|
|
7837
|
+
return { status: { available: true }, records };
|
|
7452
7838
|
}
|
|
7453
|
-
|
|
7454
|
-
|
|
7839
|
+
function unavailable2(reason) {
|
|
7840
|
+
return { status: { available: false, reason }, records: /* @__PURE__ */ new Map() };
|
|
7841
|
+
}
|
|
7842
|
+
async function runOne(field, argv, timeoutMs, opts) {
|
|
7843
|
+
const [program, ...args] = argv;
|
|
7844
|
+
const where = `(derive.commands.${field})`;
|
|
7845
|
+
if (program === void 0) {
|
|
7846
|
+
return { reason: `no program to run ${where}` };
|
|
7847
|
+
}
|
|
7848
|
+
const line = argv.join(" ");
|
|
7849
|
+
let result;
|
|
7455
7850
|
try {
|
|
7456
|
-
|
|
7851
|
+
result = await run(opts.spawn?.bin ?? program, args, {
|
|
7852
|
+
...opts.spawn,
|
|
7853
|
+
cwd: opts.spawn?.cwd ?? opts.cwd,
|
|
7854
|
+
timeoutMs
|
|
7855
|
+
});
|
|
7457
7856
|
} catch (err) {
|
|
7458
|
-
if (err
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
);
|
|
7857
|
+
if (err instanceof BinMissing) {
|
|
7858
|
+
return { reason: `\`${program}\` is not on PATH ${where}` };
|
|
7859
|
+
}
|
|
7860
|
+
return { reason: `\`${line}\` could not be run: ${errorMessage(err)} ${where}` };
|
|
7861
|
+
}
|
|
7862
|
+
if (result.timedOut) {
|
|
7863
|
+
return { reason: `\`${line}\` timed out after ${String(timeoutMs / 1e3)}s ${where}` };
|
|
7462
7864
|
}
|
|
7463
|
-
|
|
7865
|
+
if (result.tooLarge) {
|
|
7866
|
+
return {
|
|
7867
|
+
reason: `\`${line}\` wrote more than ${String(MAX_OUTPUT_BYTES / (1024 * 1024))} MiB; a command reports one field's value ${where}`
|
|
7868
|
+
};
|
|
7869
|
+
}
|
|
7870
|
+
if (result.code !== 0) {
|
|
7871
|
+
const last = lastLine(result.stderr);
|
|
7872
|
+
const detail = last === "" ? "no output on stderr" : last;
|
|
7873
|
+
const exit = result.code === null ? "killed by a signal" : `exit ${String(result.code)}`;
|
|
7874
|
+
return { reason: `\`${line}\` failed (${exit}): ${detail} ${where}` };
|
|
7875
|
+
}
|
|
7876
|
+
const value = valueOf(result.stdout);
|
|
7877
|
+
return { value: value === null ? null : { value, source: "command", evidence: line } };
|
|
7464
7878
|
}
|
|
7465
|
-
|
|
7879
|
+
|
|
7880
|
+
// src/meta/core/derive/reviews.ts
|
|
7881
|
+
import { resolve as resolvePath3 } from "node:path";
|
|
7882
|
+
function parseOriginUrl(url) {
|
|
7883
|
+
const trimmed = url.trim();
|
|
7884
|
+
if (trimmed === "") return null;
|
|
7885
|
+
if (isLocalPath(trimmed)) return null;
|
|
7886
|
+
const scp = /^(?:[^@/:]+@)?([^/:]+):(?!\/\/)(.+)$/.exec(trimmed);
|
|
7887
|
+
if (scp) {
|
|
7888
|
+
const host = scp[1];
|
|
7889
|
+
const path = scp[2];
|
|
7890
|
+
if (host === void 0 || path === void 0) return null;
|
|
7891
|
+
if (/^[A-Za-z]$/.test(host)) return null;
|
|
7892
|
+
return project(host, path);
|
|
7893
|
+
}
|
|
7894
|
+
let parsed2;
|
|
7466
7895
|
try {
|
|
7467
|
-
|
|
7468
|
-
} catch
|
|
7469
|
-
|
|
7470
|
-
`Baseline "${source}" could not be written: ${errorMessage(err)}`
|
|
7471
|
-
);
|
|
7896
|
+
parsed2 = new URL(trimmed);
|
|
7897
|
+
} catch {
|
|
7898
|
+
return null;
|
|
7472
7899
|
}
|
|
7473
|
-
|
|
7900
|
+
if (!/^(https?|ssh|git):$/.test(parsed2.protocol)) return null;
|
|
7901
|
+
if (parsed2.hostname === "") return null;
|
|
7902
|
+
return project(parsed2.hostname, parsed2.pathname);
|
|
7903
|
+
}
|
|
7904
|
+
function isLocalPath(s) {
|
|
7905
|
+
return s.startsWith("/") || s.startsWith("./") || s.startsWith("../") || s.startsWith("\\\\");
|
|
7906
|
+
}
|
|
7907
|
+
function project(host, path) {
|
|
7908
|
+
const segments = path.replace(/\.git$/, "").split("/").filter((s) => s !== "");
|
|
7909
|
+
if (segments.length < 2) return null;
|
|
7910
|
+
return { host, project: segments.join("/") };
|
|
7911
|
+
}
|
|
7912
|
+
function identityFromOrigin(host, project2, requested) {
|
|
7913
|
+
const name2 = host.toLowerCase();
|
|
7914
|
+
if (name2.includes("github")) return { kind: "github", host, project: project2 };
|
|
7915
|
+
if (name2.includes("gitlab")) return { kind: "gitlab", host, project: project2 };
|
|
7916
|
+
const only = requested !== void 0 && requested.length === 1 ? requested[0] : void 0;
|
|
7917
|
+
return only === void 0 ? null : { kind: only, host, project: project2 };
|
|
7918
|
+
}
|
|
7919
|
+
var UnknownHostClient = class {
|
|
7920
|
+
constructor(host) {
|
|
7921
|
+
this.host = host;
|
|
7922
|
+
}
|
|
7923
|
+
host;
|
|
7924
|
+
detect() {
|
|
7925
|
+
return Promise.resolve(null);
|
|
7926
|
+
}
|
|
7927
|
+
status() {
|
|
7928
|
+
return Promise.resolve({
|
|
7929
|
+
available: false,
|
|
7930
|
+
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`
|
|
7931
|
+
});
|
|
7932
|
+
}
|
|
7933
|
+
mergedChangeFor() {
|
|
7934
|
+
return Promise.resolve(null);
|
|
7935
|
+
}
|
|
7936
|
+
};
|
|
7937
|
+
function httpStatus(stderr) {
|
|
7938
|
+
const m = /\b(?:HTTP\s+(\d{3})|(\d{3})\s+Not\s+Found)\b/i.exec(stderr);
|
|
7939
|
+
const code = m?.[1] ?? m?.[2];
|
|
7940
|
+
return code === void 0 ? null : Number(code);
|
|
7941
|
+
}
|
|
7942
|
+
function isNotFound(r) {
|
|
7943
|
+
const status = httpStatus(r.stderr);
|
|
7944
|
+
return status === 404 || status === 422;
|
|
7945
|
+
}
|
|
7946
|
+
var CliClient = class {
|
|
7947
|
+
constructor(identity, spawnOpts) {
|
|
7948
|
+
this.identity = identity;
|
|
7949
|
+
this.spawnOpts = spawnOpts;
|
|
7950
|
+
}
|
|
7951
|
+
identity;
|
|
7952
|
+
spawnOpts;
|
|
7953
|
+
detect() {
|
|
7954
|
+
return Promise.resolve(this.identity);
|
|
7955
|
+
}
|
|
7956
|
+
async status() {
|
|
7957
|
+
if (this.identity === null) {
|
|
7958
|
+
return { available: false, reason: "no origin remote to tell GitHub from GitLab" };
|
|
7959
|
+
}
|
|
7960
|
+
const { host } = this.identity;
|
|
7961
|
+
let r;
|
|
7962
|
+
try {
|
|
7963
|
+
r = await this.run(["auth", "status", "--hostname", host]);
|
|
7964
|
+
} catch (err) {
|
|
7965
|
+
if (err instanceof BinMissing) {
|
|
7966
|
+
return {
|
|
7967
|
+
available: false,
|
|
7968
|
+
reason: `${this.tool} is not on PATH (origin is ${host}); install ${this.tool} and run ${this.tool} auth login`
|
|
7969
|
+
};
|
|
7970
|
+
}
|
|
7971
|
+
throw err;
|
|
7972
|
+
}
|
|
7973
|
+
if (r.timedOut) throw this.timeout(["auth", "status", "--hostname", host]);
|
|
7974
|
+
if (r.code !== 0) {
|
|
7975
|
+
return {
|
|
7976
|
+
available: false,
|
|
7977
|
+
reason: `${this.tool} is not authenticated for ${host}; run ${this.tool} auth login`
|
|
7978
|
+
};
|
|
7979
|
+
}
|
|
7980
|
+
return { available: true };
|
|
7981
|
+
}
|
|
7982
|
+
async mergedChangeFor(sha) {
|
|
7983
|
+
if (this.identity === null) return null;
|
|
7984
|
+
return this.lookup(this.identity, sha);
|
|
7985
|
+
}
|
|
7986
|
+
get bin() {
|
|
7987
|
+
return this.spawnOpts.bin ?? this.tool;
|
|
7988
|
+
}
|
|
7989
|
+
run(args) {
|
|
7990
|
+
return run(this.bin, args, this.spawnOpts);
|
|
7991
|
+
}
|
|
7992
|
+
timeout(args) {
|
|
7993
|
+
const ms = this.spawnOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
7994
|
+
return new DocmetaError(
|
|
7995
|
+
`${commandLine(this.tool, args)} timed out after ${ms}ms`
|
|
7996
|
+
);
|
|
7997
|
+
}
|
|
7998
|
+
/**
|
|
7999
|
+
* Run a call whose stdout is JSON. A not-found answer is `null`; any other
|
|
8000
|
+
* failure throws, naming the command the user can re-run by hand. The
|
|
8001
|
+
* message quotes the tool's own name rather than `bin`, which under test
|
|
8002
|
+
* is `node`.
|
|
8003
|
+
*/
|
|
8004
|
+
async json(args) {
|
|
8005
|
+
let r;
|
|
8006
|
+
try {
|
|
8007
|
+
r = await this.run(args);
|
|
8008
|
+
} catch (err) {
|
|
8009
|
+
if (err instanceof BinMissing) {
|
|
8010
|
+
throw new DocmetaError(
|
|
8011
|
+
`${commandLine(this.tool, args)} failed: ${this.tool} is not on PATH`
|
|
8012
|
+
);
|
|
8013
|
+
}
|
|
8014
|
+
throw err;
|
|
8015
|
+
}
|
|
8016
|
+
const cmd = commandLine(this.tool, args);
|
|
8017
|
+
if (r.timedOut) throw this.timeout(args);
|
|
8018
|
+
if (r.tooLarge) {
|
|
8019
|
+
throw new DocmetaError(`${cmd} wrote more output than manni will read`);
|
|
8020
|
+
}
|
|
8021
|
+
if (r.code !== 0) {
|
|
8022
|
+
if (isNotFound(r)) return null;
|
|
8023
|
+
const detail = lastLine(r.stderr) || lastLine(r.stdout);
|
|
8024
|
+
const exit = r.code === null ? "signal" : `exit ${r.code}`;
|
|
8025
|
+
throw new DocmetaError(
|
|
8026
|
+
`${cmd} failed (${exit})${detail === "" ? "" : `: ${detail}`}`
|
|
8027
|
+
);
|
|
8028
|
+
}
|
|
8029
|
+
try {
|
|
8030
|
+
return JSON.parse(r.stdout);
|
|
8031
|
+
} catch {
|
|
8032
|
+
throw new DocmetaError(`${cmd} did not return JSON`);
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
};
|
|
8036
|
+
function isRecord2(v) {
|
|
8037
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8038
|
+
}
|
|
8039
|
+
function items(v) {
|
|
8040
|
+
return Array.isArray(v) ? v.filter(isRecord2) : [];
|
|
8041
|
+
}
|
|
8042
|
+
function numberOf(v) {
|
|
8043
|
+
return typeof v === "number" && Number.isInteger(v) ? v : null;
|
|
8044
|
+
}
|
|
8045
|
+
function stringOf(v) {
|
|
8046
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
8047
|
+
}
|
|
8048
|
+
function timestampOf(v) {
|
|
8049
|
+
if (v === void 0) return void 0;
|
|
8050
|
+
return stringOf(v);
|
|
8051
|
+
}
|
|
8052
|
+
function normalizeLogin(login) {
|
|
8053
|
+
return login.replace(/\[bot\]$/, "");
|
|
8054
|
+
}
|
|
8055
|
+
function latestApprovals(reviews) {
|
|
8056
|
+
const latest = /* @__PURE__ */ new Map();
|
|
8057
|
+
for (const r of reviews) {
|
|
8058
|
+
if (r.state !== "APPROVED") continue;
|
|
8059
|
+
const key = normalizeLogin(r.login);
|
|
8060
|
+
const seen = latest.get(key);
|
|
8061
|
+
if (seen === void 0 || r.submittedAt > seen.submittedAt) {
|
|
8062
|
+
latest.set(key, { login: r.login, submittedAt: r.submittedAt });
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
return byTime([...latest.values()]);
|
|
8066
|
+
}
|
|
8067
|
+
function byTime(approvals) {
|
|
8068
|
+
return [...approvals].sort(
|
|
8069
|
+
(a, b) => a.submittedAt < b.submittedAt ? -1 : a.submittedAt > b.submittedAt ? 1 : 0
|
|
8070
|
+
);
|
|
8071
|
+
}
|
|
8072
|
+
var GitHubClient = class extends CliClient {
|
|
8073
|
+
tool = "gh";
|
|
8074
|
+
async lookup(identity, sha) {
|
|
8075
|
+
const { host, project: project2 } = identity;
|
|
8076
|
+
const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
|
|
8077
|
+
let candidates = items(await api(`repos/${project2}/commits/${sha}/pulls`)).map(
|
|
8078
|
+
(p) => ({ number: numberOf(p.number), mergedAt: timestampOf(p.merged_at) })
|
|
8079
|
+
);
|
|
8080
|
+
if (candidates.length === 0) {
|
|
8081
|
+
const found = await this.json([
|
|
8082
|
+
"pr",
|
|
8083
|
+
"list",
|
|
8084
|
+
"--search",
|
|
8085
|
+
sha,
|
|
8086
|
+
"--state",
|
|
8087
|
+
"merged",
|
|
8088
|
+
"--json",
|
|
8089
|
+
"number,mergedAt",
|
|
8090
|
+
"-R",
|
|
8091
|
+
`${host}/${project2}`
|
|
8092
|
+
]);
|
|
8093
|
+
candidates = items(found).map((p) => ({
|
|
8094
|
+
number: numberOf(p.number),
|
|
8095
|
+
mergedAt: timestampOf(p.mergedAt)
|
|
8096
|
+
}));
|
|
8097
|
+
}
|
|
8098
|
+
for (const c of candidates) {
|
|
8099
|
+
if (c.number === null) continue;
|
|
8100
|
+
let mergedAt = c.mergedAt;
|
|
8101
|
+
if (mergedAt === void 0) {
|
|
8102
|
+
const pr = await api(`repos/${project2}/pulls/${c.number}`);
|
|
8103
|
+
mergedAt = isRecord2(pr) ? stringOf(pr.merged_at) ?? null : null;
|
|
8104
|
+
}
|
|
8105
|
+
if (mergedAt === null) continue;
|
|
8106
|
+
const raw = [];
|
|
8107
|
+
for (let page = 1; ; page += 1) {
|
|
8108
|
+
const batch = items(
|
|
8109
|
+
await api(`repos/${project2}/pulls/${c.number}/reviews?per_page=100&page=${page}`)
|
|
8110
|
+
);
|
|
8111
|
+
raw.push(...batch);
|
|
8112
|
+
if (batch.length < 100) break;
|
|
8113
|
+
}
|
|
8114
|
+
const reviews = raw.map((r) => ({
|
|
8115
|
+
login: isRecord2(r.user) ? stringOf(r.user.login) : null,
|
|
8116
|
+
state: stringOf(r.state),
|
|
8117
|
+
submittedAt: stringOf(r.submitted_at)
|
|
8118
|
+
})).filter(
|
|
8119
|
+
(r) => r.login !== null && r.state !== null && r.submittedAt !== null
|
|
8120
|
+
);
|
|
8121
|
+
return { id: c.number, mergedAt, approvals: latestApprovals(reviews) };
|
|
8122
|
+
}
|
|
8123
|
+
return null;
|
|
8124
|
+
}
|
|
8125
|
+
};
|
|
8126
|
+
var GitLabClient = class extends CliClient {
|
|
8127
|
+
tool = "glab";
|
|
8128
|
+
async lookup(identity, sha) {
|
|
8129
|
+
const { host } = identity;
|
|
8130
|
+
const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
|
|
8131
|
+
const merged = items(
|
|
8132
|
+
await api(`projects/:fullpath/repository/commits/${sha}/merge_requests`)
|
|
8133
|
+
).map((mr) => ({ iid: numberOf(mr.iid), mergedAt: stringOf(mr.merged_at) })).find(
|
|
8134
|
+
(mr) => mr.iid !== null && mr.mergedAt !== null
|
|
8135
|
+
);
|
|
8136
|
+
if (merged === void 0) return null;
|
|
8137
|
+
const answer = await api(`projects/:fullpath/merge_requests/${merged.iid}/approvals`);
|
|
8138
|
+
const approvedBy = isRecord2(answer) ? items(answer.approved_by) : [];
|
|
8139
|
+
const approvals = [];
|
|
8140
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8141
|
+
for (const entry of approvedBy) {
|
|
8142
|
+
const login = isRecord2(entry.user) ? stringOf(entry.user.username) : null;
|
|
8143
|
+
if (login === null) continue;
|
|
8144
|
+
const key = normalizeLogin(login);
|
|
8145
|
+
if (seen.has(key)) continue;
|
|
8146
|
+
seen.add(key);
|
|
8147
|
+
approvals.push({ login, submittedAt: merged.mergedAt });
|
|
8148
|
+
}
|
|
8149
|
+
return { id: merged.iid, mergedAt: merged.mergedAt, approvals };
|
|
8150
|
+
}
|
|
8151
|
+
};
|
|
8152
|
+
async function createReviewClient(root, opts) {
|
|
8153
|
+
const spawnOpts = { ...opts.spawn, cwd: opts.spawn?.cwd ?? root };
|
|
8154
|
+
const origin = await originUrl(root);
|
|
8155
|
+
const parsed2 = origin === null ? null : parseOriginUrl(origin);
|
|
8156
|
+
const identity = parsed2 === null ? null : identityFromOrigin(parsed2.host, parsed2.project, opts.requested);
|
|
8157
|
+
if (parsed2 !== null && identity === null) return new UnknownHostClient(parsed2.host);
|
|
8158
|
+
const client = identity?.kind === "gitlab" ? new GitLabClient(identity, spawnOpts) : new GitHubClient(identity, spawnOpts);
|
|
8159
|
+
const cache = new ReviewCache(resolvePath3(root, opts.cacheDir), opts.cache);
|
|
8160
|
+
return cachedClient(client, cache, identity);
|
|
8161
|
+
}
|
|
8162
|
+
async function originUrl(root) {
|
|
8163
|
+
try {
|
|
8164
|
+
const r = await run("git", ["remote", "get-url", "origin"], { cwd: root });
|
|
8165
|
+
if (r.code !== 0 || r.timedOut || r.tooLarge) return null;
|
|
8166
|
+
return stringOf(r.stdout.trim());
|
|
8167
|
+
} catch {
|
|
8168
|
+
return null;
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
8171
|
+
var NOTHING = { "reviewed-by": null, "last-reviewed": null };
|
|
8172
|
+
async function deriveFromReviews(inputs, client) {
|
|
8173
|
+
const records = /* @__PURE__ */ new Map();
|
|
8174
|
+
const status = await client.status();
|
|
8175
|
+
if (!status.available) return { status, records };
|
|
8176
|
+
const identity = await client.detect();
|
|
8177
|
+
const kind = identity?.kind ?? "github";
|
|
8178
|
+
const bySha = /* @__PURE__ */ new Map();
|
|
8179
|
+
const changeFor = (sha) => {
|
|
8180
|
+
let pending = bySha.get(sha);
|
|
8181
|
+
if (pending === void 0) {
|
|
8182
|
+
pending = client.mergedChangeFor(sha);
|
|
8183
|
+
bySha.set(sha, pending);
|
|
8184
|
+
}
|
|
8185
|
+
return pending;
|
|
8186
|
+
};
|
|
8187
|
+
for (const input of inputs) {
|
|
8188
|
+
if (input.sha === null) {
|
|
8189
|
+
records.set(input.label, NOTHING);
|
|
8190
|
+
continue;
|
|
8191
|
+
}
|
|
8192
|
+
const change2 = await changeFor(input.sha);
|
|
8193
|
+
records.set(input.label, factsFrom(change2, kind));
|
|
8194
|
+
}
|
|
8195
|
+
return { status, records };
|
|
8196
|
+
}
|
|
8197
|
+
function factsFrom(change2, kind) {
|
|
8198
|
+
if (change2 === null || change2.approvals.length === 0) return NOTHING;
|
|
8199
|
+
const ordered = byTime(change2.approvals);
|
|
8200
|
+
const latest = ordered.at(-1);
|
|
8201
|
+
if (latest === void 0) return NOTHING;
|
|
8202
|
+
const evidence = kind === "gitlab" ? `gitlab MR !${change2.id}` : `github PR #${change2.id}`;
|
|
8203
|
+
const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
|
|
8204
|
+
return {
|
|
8205
|
+
"reviewed-by": {
|
|
8206
|
+
value: ordered.map((a) => a.login),
|
|
8207
|
+
source: kind,
|
|
8208
|
+
evidence
|
|
8209
|
+
},
|
|
8210
|
+
"last-reviewed": {
|
|
8211
|
+
value: utcDate(latest.submittedAt),
|
|
8212
|
+
source: kind,
|
|
8213
|
+
evidence: dateEvidence
|
|
8214
|
+
}
|
|
8215
|
+
};
|
|
8216
|
+
}
|
|
8217
|
+
function utcDate(iso) {
|
|
8218
|
+
const ms = Date.parse(iso);
|
|
8219
|
+
if (Number.isNaN(ms)) return iso;
|
|
8220
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
8221
|
+
}
|
|
8222
|
+
|
|
8223
|
+
// src/meta/core/derive/index.ts
|
|
8224
|
+
import { dirname as dirname7, resolve as resolve9 } from "node:path";
|
|
8225
|
+
|
|
8226
|
+
// src/meta/core/derive/codeowners.ts
|
|
8227
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
8228
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
8229
|
+
import { dirname as dirname5, relative as relative7, resolve as resolve8, sep as sep4 } from "node:path";
|
|
8230
|
+
import picomatch4 from "picomatch";
|
|
8231
|
+
var CODEOWNERS_LOCATIONS = [
|
|
8232
|
+
".github/CODEOWNERS",
|
|
8233
|
+
"CODEOWNERS",
|
|
8234
|
+
"docs/CODEOWNERS",
|
|
8235
|
+
".gitlab/CODEOWNERS"
|
|
8236
|
+
];
|
|
8237
|
+
function findCodeowners(root, explicit) {
|
|
8238
|
+
if (explicit !== void 0) {
|
|
8239
|
+
const path = resolve8(root, explicit);
|
|
8240
|
+
return existsSync2(path) ? path : null;
|
|
8241
|
+
}
|
|
8242
|
+
for (const loc of CODEOWNERS_LOCATIONS) {
|
|
8243
|
+
const path = resolve8(root, ...loc.split("/"));
|
|
8244
|
+
if (existsSync2(path)) return path;
|
|
8245
|
+
}
|
|
8246
|
+
return null;
|
|
8247
|
+
}
|
|
8248
|
+
var SECTION_HEADER = /^(\^?)\[([^\]]+)\](?:\[(\d+)\])?(.*)$/;
|
|
8249
|
+
var TOKEN = /(?:\\.|[^\s\\])+/g;
|
|
8250
|
+
function parseCodeowners(text2, path, label) {
|
|
8251
|
+
const rules = [];
|
|
8252
|
+
const sections = [];
|
|
8253
|
+
let current;
|
|
8254
|
+
const lines = text2.split(/\r?\n/);
|
|
8255
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
8256
|
+
const raw = lines[i] ?? "";
|
|
8257
|
+
const line = raw.trim();
|
|
8258
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
8259
|
+
if (line.startsWith("[") || line.startsWith("^[")) {
|
|
8260
|
+
const m = SECTION_HEADER.exec(line);
|
|
8261
|
+
if (!m) continue;
|
|
8262
|
+
const [, caret, rawName, approvals, rest] = m;
|
|
8263
|
+
const name2 = (rawName ?? "").trim();
|
|
8264
|
+
if (name2 === "") continue;
|
|
8265
|
+
const owners2 = tokens(rest ?? "");
|
|
8266
|
+
const existing = sections.find(
|
|
8267
|
+
(s) => s.name.toLowerCase() === name2.toLowerCase()
|
|
8268
|
+
);
|
|
8269
|
+
if (existing) {
|
|
8270
|
+
if (owners2.length > 0) existing.defaultOwners = owners2;
|
|
8271
|
+
current = existing;
|
|
8272
|
+
continue;
|
|
8273
|
+
}
|
|
8274
|
+
const section = {
|
|
8275
|
+
name: name2,
|
|
8276
|
+
optional: caret === "^",
|
|
8277
|
+
...approvals !== void 0 ? { approvals: Number(approvals) } : {},
|
|
8278
|
+
defaultOwners: owners2
|
|
8279
|
+
};
|
|
8280
|
+
sections.push(section);
|
|
8281
|
+
current = section;
|
|
8282
|
+
continue;
|
|
8283
|
+
}
|
|
8284
|
+
const [first, ...owners] = tokens(line);
|
|
8285
|
+
if (first === void 0) continue;
|
|
8286
|
+
const pattern = unescapePattern(first);
|
|
8287
|
+
if (pattern === "" || pattern === "/") continue;
|
|
8288
|
+
rules.push({
|
|
8289
|
+
pattern,
|
|
8290
|
+
owners,
|
|
8291
|
+
line: i + 1,
|
|
8292
|
+
...current ? { section: current.name } : {}
|
|
8293
|
+
});
|
|
8294
|
+
}
|
|
8295
|
+
return { path, label, rules, sections };
|
|
8296
|
+
}
|
|
8297
|
+
function tokens(s) {
|
|
8298
|
+
return s.match(TOKEN) ?? [];
|
|
8299
|
+
}
|
|
8300
|
+
function unescapePattern(token) {
|
|
8301
|
+
return token.replace(/\\ /g, " ").replace(/^\\#/, "#");
|
|
8302
|
+
}
|
|
8303
|
+
function ownersFor(file, relPath) {
|
|
8304
|
+
const matchers = matchersFor(file);
|
|
8305
|
+
const defaults = new Map(file.sections.map((s) => [s.name, s.defaultOwners]));
|
|
8306
|
+
const order = [void 0, ...file.sections.map((s) => s.name)];
|
|
8307
|
+
const winners = /* @__PURE__ */ new Map();
|
|
8308
|
+
file.rules.forEach((rule, i) => {
|
|
8309
|
+
if (matchers[i]?.(relPath)) winners.set(rule.section, rule);
|
|
8310
|
+
});
|
|
8311
|
+
if (winners.size === 0) return null;
|
|
8312
|
+
const owners = [];
|
|
8313
|
+
let line = 0;
|
|
8314
|
+
let lineFromOwner = false;
|
|
8315
|
+
for (const section of order) {
|
|
8316
|
+
const rule = winners.get(section);
|
|
8317
|
+
if (!rule) continue;
|
|
8318
|
+
const chosen = rule.owners.length > 0 ? rule.owners : (section === void 0 ? void 0 : defaults.get(section)) ?? [];
|
|
8319
|
+
if (chosen.length > 0) {
|
|
8320
|
+
line = rule.line;
|
|
8321
|
+
lineFromOwner = true;
|
|
8322
|
+
} else if (!lineFromOwner) {
|
|
8323
|
+
line = rule.line;
|
|
8324
|
+
}
|
|
8325
|
+
for (const o of chosen) if (!owners.includes(o)) owners.push(o);
|
|
8326
|
+
}
|
|
8327
|
+
return { owners, line };
|
|
8328
|
+
}
|
|
8329
|
+
var matcherCache2 = /* @__PURE__ */ new WeakMap();
|
|
8330
|
+
function matchersFor(file) {
|
|
8331
|
+
let matchers = matcherCache2.get(file);
|
|
8332
|
+
if (!matchers) {
|
|
8333
|
+
matchers = file.rules.map((r) => picomatch4(toGlobs(r.pattern), PICOMATCH));
|
|
8334
|
+
matcherCache2.set(file, matchers);
|
|
8335
|
+
}
|
|
8336
|
+
return matchers;
|
|
8337
|
+
}
|
|
8338
|
+
var PICOMATCH = { dot: true, windows: false };
|
|
8339
|
+
function toGlobs(pattern) {
|
|
8340
|
+
let p = pattern;
|
|
8341
|
+
const dirOnly = p.endsWith("/");
|
|
8342
|
+
if (dirOnly) p = p.slice(0, -1);
|
|
8343
|
+
let anchored = false;
|
|
8344
|
+
if (p.startsWith("/")) {
|
|
8345
|
+
anchored = true;
|
|
8346
|
+
p = p.slice(1);
|
|
8347
|
+
} else if (p.includes("/") && !p.startsWith("**/")) {
|
|
8348
|
+
anchored = true;
|
|
8349
|
+
}
|
|
8350
|
+
const base = anchored || p.startsWith("**/") ? p : `**/${p}`;
|
|
8351
|
+
if (dirOnly) return [`${base}/**`];
|
|
8352
|
+
const last = p.slice(p.lastIndexOf("/") + 1);
|
|
8353
|
+
return /[*?[]/.test(last) ? [base] : [base, `${base}/**`];
|
|
8354
|
+
}
|
|
8355
|
+
var NOT_FOUND_REASON = `no CODEOWNERS file found (looked for ${CODEOWNERS_LOCATIONS.join(", ")})`;
|
|
8356
|
+
async function deriveFromCodeowners(inputs, opts) {
|
|
8357
|
+
const records = /* @__PURE__ */ new Map();
|
|
8358
|
+
const explicitPath = opts.explicit === void 0 ? void 0 : resolve8(opts.configDir ?? process.cwd(), opts.explicit);
|
|
8359
|
+
if (explicitPath !== void 0 && !existsSync2(explicitPath)) {
|
|
8360
|
+
return {
|
|
8361
|
+
status: { available: false, reason: `CODEOWNERS not found at ${explicitPath}` },
|
|
8362
|
+
records
|
|
8363
|
+
};
|
|
8364
|
+
}
|
|
8365
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
8366
|
+
const rootCache = /* @__PURE__ */ new Map();
|
|
8367
|
+
for (const input of inputs) {
|
|
8368
|
+
const dir = dirname5(input.absPath);
|
|
8369
|
+
let root = rootCache.get(dir);
|
|
8370
|
+
if (root === void 0) {
|
|
8371
|
+
root = opts.rootOf(input.absPath);
|
|
8372
|
+
rootCache.set(dir, root);
|
|
8373
|
+
}
|
|
8374
|
+
if (root === null) continue;
|
|
8375
|
+
const bucket = byRoot.get(root);
|
|
8376
|
+
if (bucket) bucket.push(input);
|
|
8377
|
+
else byRoot.set(root, [input]);
|
|
8378
|
+
}
|
|
8379
|
+
let found = 0;
|
|
8380
|
+
const parsed2 = /* @__PURE__ */ new Map();
|
|
8381
|
+
for (const [root, bucket] of byRoot) {
|
|
8382
|
+
const path = explicitPath ?? findCodeowners(root);
|
|
8383
|
+
if (path === null) {
|
|
8384
|
+
for (const input of bucket) records.set(input.label, null);
|
|
8385
|
+
continue;
|
|
8386
|
+
}
|
|
8387
|
+
found += 1;
|
|
8388
|
+
const label = toPosix2(relative7(opts.configDir ?? root, path));
|
|
8389
|
+
const key = `${path}\0${label}`;
|
|
8390
|
+
let file = parsed2.get(key);
|
|
8391
|
+
if (!file) {
|
|
8392
|
+
file = readFile5(path, "utf8").then(
|
|
8393
|
+
(text2) => parseCodeowners(text2, path, label)
|
|
8394
|
+
);
|
|
8395
|
+
parsed2.set(key, file);
|
|
8396
|
+
}
|
|
8397
|
+
let owners;
|
|
8398
|
+
try {
|
|
8399
|
+
owners = await file;
|
|
8400
|
+
} catch (err) {
|
|
8401
|
+
return {
|
|
8402
|
+
status: {
|
|
8403
|
+
available: false,
|
|
8404
|
+
reason: `CODEOWNERS at ${label} could not be read: ${errorMessage(err)}`
|
|
8405
|
+
},
|
|
8406
|
+
records
|
|
8407
|
+
};
|
|
8408
|
+
}
|
|
8409
|
+
for (const input of bucket) {
|
|
8410
|
+
const rel = toPosix2(relative7(root, input.absPath));
|
|
8411
|
+
const hit = ownersFor(owners, rel);
|
|
8412
|
+
records.set(
|
|
8413
|
+
input.label,
|
|
8414
|
+
hit === null ? null : {
|
|
8415
|
+
value: hit.owners,
|
|
8416
|
+
source: "codeowners",
|
|
8417
|
+
evidence: `${label}:${hit.line}`
|
|
8418
|
+
}
|
|
8419
|
+
);
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
if (byRoot.size === 0) {
|
|
8423
|
+
return {
|
|
8424
|
+
status: { available: false, reason: "no git repository contains the documents" },
|
|
8425
|
+
records
|
|
8426
|
+
};
|
|
8427
|
+
}
|
|
8428
|
+
if (found === 0 && explicitPath === void 0) {
|
|
8429
|
+
return { status: { available: true, reason: NOT_FOUND_REASON }, records };
|
|
8430
|
+
}
|
|
8431
|
+
return { status: { available: true }, records };
|
|
8432
|
+
}
|
|
8433
|
+
function toPosix2(p) {
|
|
8434
|
+
return p.split(sep4).join("/");
|
|
8435
|
+
}
|
|
8436
|
+
|
|
8437
|
+
// src/meta/core/derive/git.ts
|
|
8438
|
+
import { constants as bufferConstants } from "node:buffer";
|
|
8439
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
8440
|
+
import { dirname as dirname6, extname as extname2, relative as relative8, sep as sep5 } from "node:path";
|
|
8441
|
+
var DEFAULT_BULK_THRESHOLD = 32;
|
|
8442
|
+
var NULL_SHA = "0".repeat(40);
|
|
8443
|
+
var MAX_GIT_OUTPUT_BYTES = Math.min(
|
|
8444
|
+
512 * 1024 * 1024,
|
|
8445
|
+
bufferConstants.MAX_STRING_LENGTH
|
|
8446
|
+
);
|
|
8447
|
+
var HistoryTooLarge = class extends Error {
|
|
8448
|
+
};
|
|
8449
|
+
var BlobsUnreadable = class extends Error {
|
|
8450
|
+
constructor(detail) {
|
|
8451
|
+
super(detail);
|
|
8452
|
+
this.detail = detail;
|
|
8453
|
+
}
|
|
8454
|
+
detail;
|
|
8455
|
+
};
|
|
8456
|
+
function bodyOf(content, fenced) {
|
|
8457
|
+
if (fenced !== true) return content;
|
|
8458
|
+
const loc = locateFrontmatter(content);
|
|
8459
|
+
if (!loc) return content;
|
|
8460
|
+
return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
|
|
8461
|
+
}
|
|
8462
|
+
async function deriveFromGit(inputs, opts) {
|
|
8463
|
+
const records = /* @__PURE__ */ new Map();
|
|
8464
|
+
const byRoot = groupByRoot(inputs);
|
|
8465
|
+
if (byRoot.size === 0) {
|
|
8466
|
+
return {
|
|
8467
|
+
status: {
|
|
8468
|
+
available: false,
|
|
8469
|
+
reason: "no git repository contains the documents"
|
|
8470
|
+
},
|
|
8471
|
+
records
|
|
8472
|
+
};
|
|
8473
|
+
}
|
|
8474
|
+
const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
|
|
8475
|
+
const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
|
|
8476
|
+
const reasons = [];
|
|
8477
|
+
for (const [root, bucket] of byRoot) {
|
|
8478
|
+
const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
|
|
8479
|
+
const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
|
|
8480
|
+
if (shallow.missing) {
|
|
8481
|
+
return {
|
|
8482
|
+
status: { available: false, reason: "git is not on PATH" },
|
|
8483
|
+
records: /* @__PURE__ */ new Map()
|
|
8484
|
+
};
|
|
8485
|
+
}
|
|
8486
|
+
if (shallow.code !== 0) {
|
|
8487
|
+
reasons.push(`git could not read the repository at ${root}`);
|
|
8488
|
+
continue;
|
|
8489
|
+
}
|
|
8490
|
+
if (text(shallow).trim() === "true") {
|
|
8491
|
+
reasons.push(
|
|
8492
|
+
`this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
|
|
8493
|
+
);
|
|
8494
|
+
continue;
|
|
8495
|
+
}
|
|
8496
|
+
let histories;
|
|
8497
|
+
let blobs;
|
|
8498
|
+
try {
|
|
8499
|
+
histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
|
|
8500
|
+
if (histories === null) {
|
|
8501
|
+
reasons.push(`git could not read the history at ${root}`);
|
|
8502
|
+
continue;
|
|
8503
|
+
}
|
|
8504
|
+
blobs = await fetchBlobs(run2, neededBlobs(histories));
|
|
8505
|
+
} catch (err) {
|
|
8506
|
+
if (err instanceof BlobsUnreadable) {
|
|
8507
|
+
reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
|
|
8508
|
+
continue;
|
|
8509
|
+
}
|
|
8510
|
+
if (!(err instanceof HistoryTooLarge)) throw err;
|
|
8511
|
+
reasons.push(`git history is too large to read in one pass (${root})`);
|
|
8512
|
+
continue;
|
|
8513
|
+
}
|
|
8514
|
+
for (const entry of bucket) {
|
|
8515
|
+
const history = histories.get(entry.rel) ?? [];
|
|
8516
|
+
records.set(
|
|
8517
|
+
entry.input.label,
|
|
8518
|
+
judge(entry.input, root, history, blobs, opts.now)
|
|
8519
|
+
);
|
|
8520
|
+
}
|
|
8521
|
+
}
|
|
8522
|
+
if (reasons.length > 0) {
|
|
8523
|
+
return {
|
|
8524
|
+
status: { available: false, reason: reasons.join("; ") },
|
|
8525
|
+
records: /* @__PURE__ */ new Map()
|
|
8526
|
+
};
|
|
8527
|
+
}
|
|
8528
|
+
return { status: { available: true }, records };
|
|
8529
|
+
}
|
|
8530
|
+
function groupByRoot(inputs) {
|
|
8531
|
+
const rootOf = /* @__PURE__ */ new Map();
|
|
8532
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
8533
|
+
for (const input of inputs) {
|
|
8534
|
+
const dir = dirname6(input.absPath);
|
|
8535
|
+
let root = rootOf.get(dir);
|
|
8536
|
+
if (root === void 0) {
|
|
8537
|
+
root = findGitRoot(dir);
|
|
8538
|
+
rootOf.set(dir, root);
|
|
8539
|
+
}
|
|
8540
|
+
if (root === null) continue;
|
|
8541
|
+
const rel = relative8(root, input.absPath).split(sep5).join("/");
|
|
8542
|
+
const bucket = byRoot.get(root);
|
|
8543
|
+
if (bucket) bucket.push({ input, rel });
|
|
8544
|
+
else byRoot.set(root, [{ input, rel }]);
|
|
8545
|
+
}
|
|
8546
|
+
return byRoot;
|
|
8547
|
+
}
|
|
8548
|
+
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)";
|
|
8549
|
+
var LOG_ARGS = [
|
|
8550
|
+
"-c",
|
|
8551
|
+
"core.quotePath=false",
|
|
8552
|
+
"log",
|
|
8553
|
+
"--raw",
|
|
8554
|
+
"-M",
|
|
8555
|
+
"--no-abbrev",
|
|
8556
|
+
RECORD_FORMAT
|
|
8557
|
+
];
|
|
8558
|
+
async function perFileHistories(run2, bucket) {
|
|
8559
|
+
const histories = /* @__PURE__ */ new Map();
|
|
8560
|
+
for (const entry of bucket) {
|
|
8561
|
+
const out = await run2([...LOG_ARGS, "--follow", "--", pathspec(entry.rel)]);
|
|
8562
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
8563
|
+
if (out.code !== 0) {
|
|
8564
|
+
if (await isUnbornHead(run2)) return histories;
|
|
8565
|
+
return null;
|
|
8566
|
+
}
|
|
8567
|
+
const attributed = attribute(parseLog(text(out)), [entry.rel]);
|
|
8568
|
+
histories.set(entry.rel, attributed.get(entry.rel) ?? []);
|
|
8569
|
+
}
|
|
8570
|
+
return histories;
|
|
8571
|
+
}
|
|
8572
|
+
async function bulkHistories(run2, bucket) {
|
|
8573
|
+
const out = await run2(LOG_ARGS);
|
|
8574
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
8575
|
+
if (out.code !== 0) {
|
|
8576
|
+
if (await isUnbornHead(run2)) return /* @__PURE__ */ new Map();
|
|
8577
|
+
return null;
|
|
8578
|
+
}
|
|
8579
|
+
return attribute(
|
|
8580
|
+
parseLog(text(out)),
|
|
8581
|
+
bucket.map((e) => e.rel)
|
|
8582
|
+
);
|
|
8583
|
+
}
|
|
8584
|
+
function pathspec(rel) {
|
|
8585
|
+
return /[*?[\]]/.test(rel) ? `:(literal)${rel}` : rel;
|
|
8586
|
+
}
|
|
8587
|
+
async function isUnbornHead(run2) {
|
|
8588
|
+
const out = await run2(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
8589
|
+
return !out.missing && out.code !== 0;
|
|
8590
|
+
}
|
|
8591
|
+
function parseLog(text2) {
|
|
8592
|
+
const records = [];
|
|
8593
|
+
for (const chunk of text2.split("")) {
|
|
8594
|
+
if (chunk === "") continue;
|
|
8595
|
+
const nl = chunk.indexOf("\n");
|
|
8596
|
+
const header = nl === -1 ? chunk : chunk.slice(0, nl);
|
|
8597
|
+
const fields = header.split("\0");
|
|
8598
|
+
const sha = fields[0];
|
|
8599
|
+
if (sha === void 0 || sha === "") continue;
|
|
8600
|
+
const raw = [];
|
|
8601
|
+
if (nl !== -1) {
|
|
8602
|
+
for (const line of chunk.slice(nl + 1).split("\n")) {
|
|
8603
|
+
const entry = parseRawLine(line);
|
|
8604
|
+
if (entry) raw.push(entry);
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
records.push({
|
|
8608
|
+
sha,
|
|
8609
|
+
authorDate: fields[1] ?? "",
|
|
8610
|
+
authorName: fields[2] ?? "",
|
|
8611
|
+
authorEmail: fields[3] ?? "",
|
|
8612
|
+
coAuthors: splitTrailers(fields[4]),
|
|
8613
|
+
reviewedBy: splitTrailers(fields[5]),
|
|
8614
|
+
raw
|
|
8615
|
+
});
|
|
8616
|
+
}
|
|
8617
|
+
return records;
|
|
8618
|
+
}
|
|
8619
|
+
function splitTrailers(field) {
|
|
8620
|
+
if (field === void 0 || field === "") return [];
|
|
8621
|
+
return field.split("").map((s) => s.trim()).filter((s) => s !== "");
|
|
8622
|
+
}
|
|
8623
|
+
function parseRawLine(line) {
|
|
8624
|
+
if (!line.startsWith(":")) return null;
|
|
8625
|
+
const tab = line.indexOf(" ");
|
|
8626
|
+
if (tab === -1) return null;
|
|
8627
|
+
const meta = line.slice(1, tab).split(" ");
|
|
8628
|
+
const oldBlob = meta[2];
|
|
8629
|
+
const newBlob = meta[3];
|
|
8630
|
+
const status = meta[4];
|
|
8631
|
+
if (oldBlob === void 0 || newBlob === void 0 || status === void 0) {
|
|
8632
|
+
return null;
|
|
8633
|
+
}
|
|
8634
|
+
const paths = line.slice(tab + 1).split(" ").map(unquotePath);
|
|
8635
|
+
const first = paths[0];
|
|
8636
|
+
if (first === void 0) return null;
|
|
8637
|
+
const second = paths[1];
|
|
8638
|
+
const renamed = status.startsWith("R") || status.startsWith("C");
|
|
8639
|
+
return {
|
|
8640
|
+
status,
|
|
8641
|
+
oldBlob,
|
|
8642
|
+
newBlob,
|
|
8643
|
+
oldPath: first,
|
|
8644
|
+
newPath: renamed && second !== void 0 ? second : first
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
function unquotePath(p) {
|
|
8648
|
+
if (p.length < 2 || !p.startsWith('"') || !p.endsWith('"')) return p;
|
|
8649
|
+
const inner = p.slice(1, -1);
|
|
8650
|
+
const bytes = [];
|
|
8651
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
8652
|
+
const ch = inner[i];
|
|
8653
|
+
if (ch !== "\\") {
|
|
8654
|
+
bytes.push(...Buffer.from(ch ?? "", "utf8"));
|
|
8655
|
+
continue;
|
|
8656
|
+
}
|
|
8657
|
+
const next = inner[i + 1];
|
|
8658
|
+
if (next === void 0) break;
|
|
8659
|
+
const octal = /^[0-7]{3}/.exec(inner.slice(i + 1));
|
|
8660
|
+
if (octal) {
|
|
8661
|
+
bytes.push(parseInt(octal[0], 8));
|
|
8662
|
+
i += 3;
|
|
8663
|
+
continue;
|
|
8664
|
+
}
|
|
8665
|
+
const simple = {
|
|
8666
|
+
a: 7,
|
|
8667
|
+
b: 8,
|
|
8668
|
+
t: 9,
|
|
8669
|
+
n: 10,
|
|
8670
|
+
v: 11,
|
|
8671
|
+
f: 12,
|
|
8672
|
+
r: 13,
|
|
8673
|
+
'"': 34,
|
|
8674
|
+
"\\": 92
|
|
8675
|
+
};
|
|
8676
|
+
const code = simple[next];
|
|
8677
|
+
if (code === void 0) bytes.push(...Buffer.from(next, "utf8"));
|
|
8678
|
+
else bytes.push(code);
|
|
8679
|
+
i += 1;
|
|
8680
|
+
}
|
|
8681
|
+
return Buffer.from(bytes).toString("utf8");
|
|
8682
|
+
}
|
|
8683
|
+
function attribute(records, wanted) {
|
|
8684
|
+
const histories = /* @__PURE__ */ new Map();
|
|
8685
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
8686
|
+
for (const rel of wanted) {
|
|
8687
|
+
histories.set(rel, []);
|
|
8688
|
+
tracked.set(rel, rel);
|
|
8689
|
+
}
|
|
8690
|
+
for (const record of records) {
|
|
8691
|
+
if (record.raw.length === 0) continue;
|
|
8692
|
+
const byNewPath = /* @__PURE__ */ new Map();
|
|
8693
|
+
for (const entry of record.raw) byNewPath.set(entry.newPath, entry);
|
|
8694
|
+
for (const [rel, current] of tracked) {
|
|
8695
|
+
const entry = byNewPath.get(current);
|
|
8696
|
+
if (!entry) continue;
|
|
8697
|
+
histories.get(rel)?.push({
|
|
8698
|
+
sha: record.sha,
|
|
8699
|
+
authorDate: record.authorDate,
|
|
8700
|
+
authorName: record.authorName,
|
|
8701
|
+
authorEmail: record.authorEmail,
|
|
8702
|
+
coAuthors: record.coAuthors,
|
|
8703
|
+
reviewedBy: record.reviewedBy,
|
|
8704
|
+
oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
|
|
8705
|
+
newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
|
|
8706
|
+
pathAtCommit: entry.newPath
|
|
8707
|
+
});
|
|
8708
|
+
if (entry.oldPath !== entry.newPath) tracked.set(rel, entry.oldPath);
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
return histories;
|
|
8712
|
+
}
|
|
8713
|
+
function neededBlobs(histories) {
|
|
8714
|
+
const shas = /* @__PURE__ */ new Set();
|
|
8715
|
+
for (const history of histories.values()) {
|
|
8716
|
+
for (const h of history) {
|
|
8717
|
+
if (h.oldBlob !== null) shas.add(h.oldBlob);
|
|
8718
|
+
if (h.newBlob !== null) shas.add(h.newBlob);
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
return shas;
|
|
8722
|
+
}
|
|
8723
|
+
async function fetchBlobs(run2, shas) {
|
|
8724
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
8725
|
+
if (shas.size === 0) return blobs;
|
|
8726
|
+
const out = await run2(["cat-file", "--batch"], `${[...shas].join("\n")}
|
|
8727
|
+
`);
|
|
8728
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
8729
|
+
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
8730
|
+
const buf = out.raw;
|
|
8731
|
+
let cursor = 0;
|
|
8732
|
+
while (cursor < buf.length) {
|
|
8733
|
+
const nl = buf.indexOf(10, cursor);
|
|
8734
|
+
if (nl === -1) break;
|
|
8735
|
+
const header = buf.toString("utf8", cursor, nl).split(" ");
|
|
8736
|
+
cursor = nl + 1;
|
|
8737
|
+
const sha = header[0];
|
|
8738
|
+
const kind = header[1];
|
|
8739
|
+
const size = Number(header[2]);
|
|
8740
|
+
if (sha === void 0 || kind !== "blob" || !Number.isFinite(size)) {
|
|
8741
|
+
continue;
|
|
8742
|
+
}
|
|
8743
|
+
blobs.set(sha, buf.toString("utf8", cursor, cursor + size));
|
|
8744
|
+
cursor += size + 1;
|
|
8745
|
+
}
|
|
8746
|
+
return blobs;
|
|
8747
|
+
}
|
|
8748
|
+
var git = (value, evidence) => ({
|
|
8749
|
+
value,
|
|
8750
|
+
source: "git",
|
|
8751
|
+
evidence
|
|
8752
|
+
});
|
|
8753
|
+
function judge(input, root, history, blobs, now) {
|
|
8754
|
+
const fenced = input.extracted.fenced;
|
|
8755
|
+
const extractor = extractorFor(input);
|
|
8756
|
+
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
8757
|
+
const stampsOf = (sha) => {
|
|
8758
|
+
if (sha === null) return {};
|
|
8759
|
+
const content = blobs.get(sha);
|
|
8760
|
+
return content === void 0 ? {} : extractStamps(extractor, content, input.absPath);
|
|
8761
|
+
};
|
|
8762
|
+
const bodyChanging = [...history].reverse().filter((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
|
|
8763
|
+
const oldest = history[history.length - 1];
|
|
8764
|
+
const newestBody = bodyChanging[bodyChanging.length - 1];
|
|
8765
|
+
const head = history[0];
|
|
8766
|
+
const headBlob = head === void 0 ? null : head.newBlob;
|
|
8767
|
+
const created = oldest === void 0 ? null : createdOf(oldest, stampsOf(oldest.newBlob));
|
|
8768
|
+
const workingChanged = !sameBody(
|
|
8769
|
+
bodyOf(input.content, fenced),
|
|
8770
|
+
headBlob === null ? null : blobBody(headBlob)
|
|
8771
|
+
);
|
|
8772
|
+
let lastUpdated;
|
|
8773
|
+
let lastBodyCommit;
|
|
8774
|
+
if (workingChanged) {
|
|
8775
|
+
lastBodyCommit = null;
|
|
8776
|
+
const working = input.extracted.data["last-updated"];
|
|
8777
|
+
const committed = stampsOf(headBlob)["last-updated"];
|
|
8778
|
+
lastUpdated = working !== void 0 && working !== null && !sameValue(working, committed) ? git(working, "stamped in working tree") : git(localDate(now()), "uncommitted body change");
|
|
8779
|
+
} else if (newestBody === void 0) {
|
|
8780
|
+
lastBodyCommit = null;
|
|
8781
|
+
lastUpdated = null;
|
|
8782
|
+
} else {
|
|
8783
|
+
lastBodyCommit = newestBody.sha;
|
|
8784
|
+
lastUpdated = lastUpdatedOf(
|
|
8785
|
+
newestBody,
|
|
8786
|
+
stampsOf(newestBody.newBlob),
|
|
8787
|
+
stampsOf(newestBody.oldBlob)
|
|
8788
|
+
);
|
|
8789
|
+
}
|
|
8790
|
+
const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging);
|
|
8791
|
+
let reviewedBy = null;
|
|
8792
|
+
let lastReviewed = null;
|
|
8793
|
+
if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
|
|
8794
|
+
const evidence = `Reviewed-by trailer in ${short(newestBody.sha)}`;
|
|
8795
|
+
reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
|
|
8796
|
+
lastReviewed = git(datePart(newestBody.authorDate), evidence);
|
|
8797
|
+
}
|
|
8798
|
+
return {
|
|
8799
|
+
created,
|
|
8800
|
+
"last-updated": lastUpdated,
|
|
8801
|
+
authors,
|
|
8802
|
+
"reviewed-by": reviewedBy,
|
|
8803
|
+
"last-reviewed": lastReviewed,
|
|
8804
|
+
lastBodyCommit,
|
|
8805
|
+
root
|
|
8806
|
+
};
|
|
8807
|
+
}
|
|
8808
|
+
function createdOf(oldest, stamps) {
|
|
8809
|
+
const stamp = stamps["created"];
|
|
8810
|
+
if (stamp !== void 0 && stamp !== null) {
|
|
8811
|
+
return git(stamp, `stamped in ${short(oldest.sha)}`);
|
|
8812
|
+
}
|
|
8813
|
+
const date = datePart(oldest.authorDate);
|
|
8814
|
+
return git(date, `added in ${short(oldest.sha)} (${date})`);
|
|
8815
|
+
}
|
|
8816
|
+
function lastUpdatedOf(commit, after, before) {
|
|
8817
|
+
const stamp = after["last-updated"];
|
|
8818
|
+
if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before["last-updated"])) {
|
|
8819
|
+
return git(stamp, `stamped in ${short(commit.sha)}`);
|
|
8820
|
+
}
|
|
8821
|
+
const date = datePart(commit.authorDate);
|
|
8822
|
+
return git(date, `body changed in ${short(commit.sha)} (${date})`);
|
|
8823
|
+
}
|
|
8824
|
+
function authorsOf(bodyChanging) {
|
|
8825
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8826
|
+
const names = [];
|
|
8827
|
+
const add = (name2, email) => {
|
|
8828
|
+
if (name2 === "" || name2.endsWith("[bot]")) return;
|
|
8829
|
+
const key = email === null || email === "" ? `name:${name2.toLowerCase()}` : email.toLowerCase();
|
|
8830
|
+
if (seen.has(key)) return;
|
|
8831
|
+
seen.add(key);
|
|
8832
|
+
names.push(name2);
|
|
8833
|
+
};
|
|
8834
|
+
for (const h of bodyChanging) {
|
|
8835
|
+
add(h.authorName, h.authorEmail);
|
|
8836
|
+
for (const co of h.coAuthors) add(nameOf(co), emailOf(co));
|
|
8837
|
+
}
|
|
8838
|
+
const n = bodyChanging.length;
|
|
8839
|
+
return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
|
|
8840
|
+
}
|
|
8841
|
+
function extractorFor(input) {
|
|
8842
|
+
return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
|
|
8843
|
+
}
|
|
8844
|
+
function extractStamps(extractor, content, filePath) {
|
|
8845
|
+
if (!extractor) return {};
|
|
8846
|
+
try {
|
|
8847
|
+
return extractor.extract(content, filePath).data;
|
|
8848
|
+
} catch {
|
|
8849
|
+
return {};
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
function sameBody(a, b) {
|
|
8853
|
+
if (a === null || b === null) return a === b;
|
|
8854
|
+
return a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
|
|
8855
|
+
}
|
|
8856
|
+
function sameValue(a, b) {
|
|
8857
|
+
return toJsonText(a) === toJsonText(b);
|
|
8858
|
+
}
|
|
8859
|
+
var short = (sha) => sha.slice(0, 7);
|
|
8860
|
+
var datePart = (iso) => iso.slice(0, 10);
|
|
8861
|
+
function localDate(d) {
|
|
8862
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
8863
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
8864
|
+
}
|
|
8865
|
+
function nameOf(trailer) {
|
|
8866
|
+
const lt = trailer.lastIndexOf("<");
|
|
8867
|
+
return (lt === -1 ? trailer : trailer.slice(0, lt)).trim();
|
|
8868
|
+
}
|
|
8869
|
+
function emailOf(trailer) {
|
|
8870
|
+
const m = /<([^>]*)>\s*$/.exec(trailer);
|
|
8871
|
+
return m?.[1]?.trim() ?? null;
|
|
8872
|
+
}
|
|
8873
|
+
function text(out) {
|
|
8874
|
+
try {
|
|
8875
|
+
return out.raw.toString("utf8");
|
|
8876
|
+
} catch {
|
|
8877
|
+
throw new HistoryTooLarge();
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
function runGit(args, cwd, stdin, maxBytes) {
|
|
8881
|
+
return new Promise((settle, reject) => {
|
|
8882
|
+
let done = false;
|
|
8883
|
+
const finish = (out) => {
|
|
8884
|
+
if (done) return;
|
|
8885
|
+
done = true;
|
|
8886
|
+
settle(out);
|
|
8887
|
+
};
|
|
8888
|
+
const fail = (err) => {
|
|
8889
|
+
if (done) return;
|
|
8890
|
+
done = true;
|
|
8891
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
8892
|
+
};
|
|
8893
|
+
const failed = (missing) => ({
|
|
8894
|
+
code: null,
|
|
8895
|
+
raw: Buffer.alloc(0),
|
|
8896
|
+
missing,
|
|
8897
|
+
tooLarge: false
|
|
8898
|
+
});
|
|
8899
|
+
let child;
|
|
8900
|
+
try {
|
|
8901
|
+
child = spawn3("git", args, { cwd, windowsHide: true });
|
|
8902
|
+
} catch {
|
|
8903
|
+
finish(failed(true));
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8906
|
+
let chunks = [];
|
|
8907
|
+
let total = 0;
|
|
8908
|
+
let tooLarge = false;
|
|
8909
|
+
child.stdout.on("data", (chunk) => {
|
|
8910
|
+
if (tooLarge) return;
|
|
8911
|
+
total += chunk.length;
|
|
8912
|
+
if (total > maxBytes) {
|
|
8913
|
+
tooLarge = true;
|
|
8914
|
+
chunks = [];
|
|
8915
|
+
child.kill();
|
|
8916
|
+
return;
|
|
8917
|
+
}
|
|
8918
|
+
chunks.push(chunk);
|
|
8919
|
+
});
|
|
8920
|
+
child.stderr.resume();
|
|
8921
|
+
child.on("error", (err) => {
|
|
8922
|
+
finish(failed(err.code === "ENOENT"));
|
|
8923
|
+
});
|
|
8924
|
+
child.stdin.on("error", () => {
|
|
8925
|
+
});
|
|
8926
|
+
child.on("close", (code) => {
|
|
8927
|
+
try {
|
|
8928
|
+
const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
|
|
8929
|
+
finish({ code, raw, missing: false, tooLarge });
|
|
8930
|
+
} catch (err) {
|
|
8931
|
+
fail(err);
|
|
8932
|
+
}
|
|
8933
|
+
});
|
|
8934
|
+
if (stdin === void 0) child.stdin.end();
|
|
8935
|
+
else child.stdin.end(stdin);
|
|
8936
|
+
});
|
|
8937
|
+
}
|
|
8938
|
+
|
|
8939
|
+
// src/meta/core/derive/index.ts
|
|
8940
|
+
var FIELD_SOURCES = {
|
|
8941
|
+
created: ["git"],
|
|
8942
|
+
"last-updated": ["git"],
|
|
8943
|
+
authors: ["git"],
|
|
8944
|
+
owner: ["codeowners"],
|
|
8945
|
+
"reviewed-by": ["github", "gitlab", "git"],
|
|
8946
|
+
"last-reviewed": ["github", "gitlab", "git"]
|
|
8947
|
+
};
|
|
8948
|
+
var REVIEW_SOURCES = ["github", "gitlab"];
|
|
8949
|
+
var HOST_NAME = {
|
|
8950
|
+
github: "GitHub",
|
|
8951
|
+
gitlab: "GitLab"
|
|
8952
|
+
};
|
|
8953
|
+
function sourcesFor(field, commands) {
|
|
8954
|
+
if (isBuiltinField(field)) return FIELD_SOURCES[field];
|
|
8955
|
+
if (commands !== void 0 && Object.hasOwn(commands, field)) return ["command"];
|
|
8956
|
+
return [];
|
|
8957
|
+
}
|
|
8958
|
+
function consultedSources(fields, sources, commands) {
|
|
8959
|
+
const needed = /* @__PURE__ */ new Set();
|
|
8960
|
+
for (const field of fields) {
|
|
8961
|
+
for (const s of sourcesFor(field, commands)) needed.add(s);
|
|
8962
|
+
}
|
|
8963
|
+
return sources.filter((s) => needed.has(s));
|
|
8964
|
+
}
|
|
8965
|
+
function assertSourcesAvailable(sources, hint) {
|
|
8966
|
+
for (const name2 of Object.keys(sources)) {
|
|
8967
|
+
const status = sources[name2];
|
|
8968
|
+
if (status === void 0 || status.available) continue;
|
|
8969
|
+
const reason = status.reason ?? "it could not answer";
|
|
8970
|
+
throw new DocmetaError(
|
|
8971
|
+
`${name2} source unavailable: ${reason}${hint === void 0 ? "" : `; ${hint}`}`
|
|
8972
|
+
);
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
async function deriveMetadata(inputs, ctx) {
|
|
8976
|
+
const consulted = new Set(consultedSources(ctx.fields, ctx.sources, ctx.commands));
|
|
8977
|
+
const sources = {};
|
|
8978
|
+
let git2 = /* @__PURE__ */ new Map();
|
|
8979
|
+
if (consulted.has("git")) {
|
|
8980
|
+
const result = await deriveFromGit(inputs, { cwd: ctx.cwd, now: ctx.now });
|
|
8981
|
+
sources.git = result.status;
|
|
8982
|
+
git2 = result.records;
|
|
8983
|
+
}
|
|
8984
|
+
const rootCache = /* @__PURE__ */ new Map();
|
|
8985
|
+
const rootOf = (absPath) => {
|
|
8986
|
+
const dir = dirname7(absPath);
|
|
8987
|
+
let root = rootCache.get(dir);
|
|
8988
|
+
if (root === void 0) {
|
|
8989
|
+
root = findGitRoot(dir);
|
|
8990
|
+
rootCache.set(dir, root);
|
|
8991
|
+
}
|
|
8992
|
+
return root;
|
|
8993
|
+
};
|
|
8994
|
+
const rootFor = (input) => git2.get(input.label)?.root ?? rootOf(input.absPath);
|
|
8995
|
+
let owners = /* @__PURE__ */ new Map();
|
|
8996
|
+
if (consulted.has("codeowners")) {
|
|
8997
|
+
const result = await deriveFromCodeowners(inputs, {
|
|
8998
|
+
explicit: ctx.codeowners,
|
|
8999
|
+
configDir: ctx.configDir,
|
|
9000
|
+
rootOf
|
|
9001
|
+
});
|
|
9002
|
+
sources.codeowners = result.status;
|
|
9003
|
+
owners = result.records;
|
|
9004
|
+
}
|
|
9005
|
+
const requested = REVIEW_SOURCES.filter((s) => consulted.has(s));
|
|
9006
|
+
let reviews = /* @__PURE__ */ new Map();
|
|
9007
|
+
if (requested.length > 0) {
|
|
9008
|
+
if (!consulted.has("git")) {
|
|
9009
|
+
for (const s of requested) {
|
|
9010
|
+
sources[s] = {
|
|
9011
|
+
available: false,
|
|
9012
|
+
reason: `the ${s} source needs the git source to find each document's commits; add git to sources`
|
|
9013
|
+
};
|
|
9014
|
+
}
|
|
9015
|
+
} else if (sources.git?.available === false) {
|
|
9016
|
+
for (const s of requested) {
|
|
9017
|
+
sources[s] = {
|
|
9018
|
+
available: false,
|
|
9019
|
+
reason: `the git source could not answer, so the ${s} source cannot locate any commit`
|
|
9020
|
+
};
|
|
9021
|
+
}
|
|
9022
|
+
} else {
|
|
9023
|
+
const result = await deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested);
|
|
9024
|
+
Object.assign(sources, result.status);
|
|
9025
|
+
reviews = result.records;
|
|
9026
|
+
}
|
|
9027
|
+
}
|
|
9028
|
+
let commanded = /* @__PURE__ */ new Map();
|
|
9029
|
+
const doomed = Object.values(sources).some((s) => !s.available);
|
|
9030
|
+
if (consulted.has("command") && ctx.commands !== void 0 && !doomed) {
|
|
9031
|
+
const wanted = new Set(ctx.fields);
|
|
9032
|
+
const commands = Object.fromEntries(
|
|
9033
|
+
Object.entries(ctx.commands).filter(([field]) => wanted.has(field))
|
|
9034
|
+
);
|
|
9035
|
+
if (Object.keys(commands).length > 0) {
|
|
9036
|
+
const result = await deriveFromCommands(inputs, commands, { cwd: ctx.configDir ?? ctx.cwd });
|
|
9037
|
+
sources.command = result.status;
|
|
9038
|
+
commanded = result.records;
|
|
9039
|
+
}
|
|
9040
|
+
}
|
|
9041
|
+
const records = /* @__PURE__ */ new Map();
|
|
9042
|
+
for (const input of inputs) {
|
|
9043
|
+
const fields = {};
|
|
9044
|
+
const g = git2.get(input.label);
|
|
9045
|
+
const r = reviews.get(input.label);
|
|
9046
|
+
const c = commanded.get(input.label);
|
|
9047
|
+
for (const field of ctx.fields) {
|
|
9048
|
+
let value = null;
|
|
9049
|
+
for (const source of sourcesFor(field, ctx.commands)) {
|
|
9050
|
+
if (!consulted.has(source)) continue;
|
|
9051
|
+
const candidate = valueFrom(source, field, g, r, owners.get(input.label), c);
|
|
9052
|
+
if (candidate !== null) {
|
|
9053
|
+
value = candidate;
|
|
9054
|
+
break;
|
|
9055
|
+
}
|
|
9056
|
+
}
|
|
9057
|
+
fields[field] = value;
|
|
9058
|
+
}
|
|
9059
|
+
records.set(input.label, { file: input.label, fields });
|
|
9060
|
+
}
|
|
9061
|
+
return { records, sources };
|
|
9062
|
+
}
|
|
9063
|
+
function valueFrom(source, field, git2, reviews, owner, commanded) {
|
|
9064
|
+
switch (source) {
|
|
9065
|
+
case "git":
|
|
9066
|
+
if (git2 === void 0 || !isBuiltinField(field) || field === "owner") return null;
|
|
9067
|
+
return git2[field];
|
|
9068
|
+
case "command":
|
|
9069
|
+
return commanded?.[field] ?? null;
|
|
9070
|
+
case "codeowners":
|
|
9071
|
+
return field === "owner" ? owner ?? null : null;
|
|
9072
|
+
case "github":
|
|
9073
|
+
case "gitlab": {
|
|
9074
|
+
if (reviews === void 0) return null;
|
|
9075
|
+
if (field !== "reviewed-by" && field !== "last-reviewed") return null;
|
|
9076
|
+
const fact = reviews[field];
|
|
9077
|
+
return fact !== null && fact.source === source ? fact : null;
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
async function deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested) {
|
|
9082
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
9083
|
+
for (const input of inputs) {
|
|
9084
|
+
const root = rootFor(input);
|
|
9085
|
+
if (root === null) continue;
|
|
9086
|
+
const bucket = byRoot.get(root) ?? [];
|
|
9087
|
+
bucket.push(input);
|
|
9088
|
+
byRoot.set(root, bucket);
|
|
9089
|
+
}
|
|
9090
|
+
const status = {};
|
|
9091
|
+
const records = /* @__PURE__ */ new Map();
|
|
9092
|
+
const unavailable3 = (which, reason) => {
|
|
9093
|
+
for (const s of which) status[s] = { available: false, reason };
|
|
9094
|
+
return { status, records: /* @__PURE__ */ new Map() };
|
|
9095
|
+
};
|
|
9096
|
+
if (byRoot.size === 0) {
|
|
9097
|
+
return unavailable3(requested, "no git repository contains the documents");
|
|
9098
|
+
}
|
|
9099
|
+
const cacheDir = resolve9(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
|
|
9100
|
+
for (const [root, bucket] of byRoot) {
|
|
9101
|
+
const client = ctx.reviews ?? await createReviewClient(root, { cwd: ctx.cwd, cache: ctx.cache, cacheDir, requested });
|
|
9102
|
+
const identity = await client.detect();
|
|
9103
|
+
if (identity === null) {
|
|
9104
|
+
const s = await client.status();
|
|
9105
|
+
return unavailable3(requested, s.reason ?? "no origin remote to tell GitHub from GitLab");
|
|
9106
|
+
}
|
|
9107
|
+
const source = identity.kind;
|
|
9108
|
+
if (!requested.includes(source)) {
|
|
9109
|
+
return unavailable3(
|
|
9110
|
+
requested,
|
|
9111
|
+
`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`
|
|
9112
|
+
);
|
|
9113
|
+
}
|
|
9114
|
+
const result = await deriveFromReviews(
|
|
9115
|
+
bucket.map((input) => ({
|
|
9116
|
+
label: input.label,
|
|
9117
|
+
sha: git2.get(input.label)?.lastBodyCommit ?? null
|
|
9118
|
+
})),
|
|
9119
|
+
client
|
|
9120
|
+
);
|
|
9121
|
+
if (!result.status.available) {
|
|
9122
|
+
status[source] = result.status;
|
|
9123
|
+
return { status, records: /* @__PURE__ */ new Map() };
|
|
9124
|
+
}
|
|
9125
|
+
status[source] = result.status;
|
|
9126
|
+
for (const [label, facts] of result.records) records.set(label, facts);
|
|
9127
|
+
}
|
|
9128
|
+
return { status, records };
|
|
9129
|
+
}
|
|
9130
|
+
|
|
9131
|
+
// src/meta/core/derive/config.ts
|
|
9132
|
+
var DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
|
|
9133
|
+
function commandsOf(config) {
|
|
9134
|
+
if (config?.commands === void 0) return void 0;
|
|
9135
|
+
const out = {};
|
|
9136
|
+
for (const [field, c] of Object.entries(config.commands)) {
|
|
9137
|
+
out[field] = {
|
|
9138
|
+
run: c.run,
|
|
9139
|
+
timeoutMs: c.timeout === void 0 ? DEFAULT_COMMAND_TIMEOUT_MS : Math.round(c.timeout * 1e3)
|
|
9140
|
+
};
|
|
9141
|
+
}
|
|
9142
|
+
return out;
|
|
9143
|
+
}
|
|
9144
|
+
|
|
9145
|
+
// src/meta/core/derive/table.ts
|
|
9146
|
+
var DERIVED_VIEW = "derived";
|
|
9147
|
+
var DERIVED_ROWS = "_derived_rows";
|
|
9148
|
+
var RESOLVED_VIEW = "resolved";
|
|
9149
|
+
var ORIGIN_COLUMN = "_origin";
|
|
9150
|
+
var SOURCES_COLUMN = "_sources";
|
|
9151
|
+
function derivedColumns(commands) {
|
|
9152
|
+
return ["_path", ...derivableFields(commands), SOURCES_COLUMN];
|
|
9153
|
+
}
|
|
9154
|
+
function mentionsDerived(sql) {
|
|
9155
|
+
return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
|
|
9156
|
+
}
|
|
9157
|
+
function fieldsForSql(sql, fields) {
|
|
9158
|
+
if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) return [...fields];
|
|
9159
|
+
return fields.filter(
|
|
9160
|
+
(field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql)
|
|
9161
|
+
);
|
|
9162
|
+
}
|
|
9163
|
+
function escapeRegExp(s) {
|
|
9164
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9165
|
+
}
|
|
9166
|
+
function createDerivedView(db, records, columns) {
|
|
9167
|
+
const quoted = columns.map(quoteIdent);
|
|
9168
|
+
db.exec(
|
|
9169
|
+
`CREATE TABLE ${DERIVED_ROWS} (${quoted.map((c, i) => i === 0 ? `${c} TEXT PRIMARY KEY` : `${c} TEXT`).join(", ")})`
|
|
9170
|
+
);
|
|
9171
|
+
const insert = db.prepare(
|
|
9172
|
+
`INSERT INTO ${DERIVED_ROWS} VALUES (${quoted.map(() => "?").join(", ")})`
|
|
9173
|
+
);
|
|
9174
|
+
const fields = columns.slice(1, -1);
|
|
9175
|
+
db.exec("BEGIN");
|
|
9176
|
+
let committed = false;
|
|
9177
|
+
try {
|
|
9178
|
+
for (const [label, record] of records) {
|
|
9179
|
+
const sources = {};
|
|
9180
|
+
const values = fields.map((field) => {
|
|
9181
|
+
const derived = record.fields[field];
|
|
9182
|
+
if (derived == null) return null;
|
|
9183
|
+
sources[field] = { source: derived.source, evidence: derived.evidence };
|
|
9184
|
+
return bindValue(derived.value);
|
|
9185
|
+
});
|
|
9186
|
+
insert.run(label, ...values, JSON.stringify(sources));
|
|
9187
|
+
}
|
|
9188
|
+
db.exec("COMMIT");
|
|
9189
|
+
committed = true;
|
|
9190
|
+
} finally {
|
|
9191
|
+
if (!committed) db.exec("ROLLBACK");
|
|
9192
|
+
}
|
|
9193
|
+
db.exec(`CREATE VIEW ${DERIVED_VIEW} AS SELECT * FROM ${DERIVED_ROWS}`);
|
|
9194
|
+
}
|
|
9195
|
+
function resolvedColumns(docsColumns, commands) {
|
|
9196
|
+
const columns = ["_path"];
|
|
9197
|
+
const seen = /* @__PURE__ */ new Set(["_path", ORIGIN_COLUMN, SOURCES_COLUMN]);
|
|
9198
|
+
for (const column of docsColumns) {
|
|
9199
|
+
if (seen.has(column) || RESERVED.has(column)) continue;
|
|
9200
|
+
seen.add(column);
|
|
9201
|
+
columns.push(column);
|
|
9202
|
+
}
|
|
9203
|
+
for (const field of derivableFields(commands)) {
|
|
9204
|
+
if (seen.has(field)) continue;
|
|
9205
|
+
seen.add(field);
|
|
9206
|
+
columns.push(field);
|
|
9207
|
+
}
|
|
9208
|
+
columns.push(ORIGIN_COLUMN, SOURCES_COLUMN);
|
|
9209
|
+
return columns;
|
|
9210
|
+
}
|
|
9211
|
+
function mentionsResolved(sql) {
|
|
9212
|
+
return /\b(?:from|join|update|into|table|view)\s+(?:"resolved"|resolved\b)/i.test(sql);
|
|
9213
|
+
}
|
|
9214
|
+
function jsonPath(field) {
|
|
9215
|
+
return `$.${JSON.stringify(field)}`;
|
|
9216
|
+
}
|
|
9217
|
+
function sqlString(value) {
|
|
9218
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
9219
|
+
}
|
|
9220
|
+
function createResolvedView(db, docsColumns, commands) {
|
|
9221
|
+
const columns = resolvedColumns(docsColumns, commands);
|
|
9222
|
+
const fields = columns.slice(1, -2);
|
|
9223
|
+
const inDocs = new Set(docsColumns);
|
|
9224
|
+
const inDerived = new Set(derivableFields(commands));
|
|
9225
|
+
const selects = [`d.${quoteIdent("_path")} AS ${quoteIdent("_path")}`];
|
|
9226
|
+
const origin = [];
|
|
9227
|
+
for (const field of fields) {
|
|
9228
|
+
const path = sqlString(jsonPath(field));
|
|
9229
|
+
const asserted = `json_type(d.${quoteIdent("_data")}, ${path}) IS NOT NULL`;
|
|
9230
|
+
const assertedValue = inDocs.has(field) ? `d.${quoteIdent(field)}` : `d.${quoteIdent("_data")} ->> ${path}`;
|
|
9231
|
+
if (inDerived.has(field)) {
|
|
9232
|
+
const derivedValue = `r.${quoteIdent(field)}`;
|
|
9233
|
+
selects.push(
|
|
9234
|
+
`CASE WHEN ${asserted} THEN ${assertedValue} ELSE ${derivedValue} END AS ${quoteIdent(field)}`
|
|
9235
|
+
);
|
|
9236
|
+
origin.push(
|
|
9237
|
+
`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' WHEN ${derivedValue} IS NOT NULL THEN 'derived' END`
|
|
9238
|
+
);
|
|
9239
|
+
} else {
|
|
9240
|
+
selects.push(`${assertedValue} AS ${quoteIdent(field)}`);
|
|
9241
|
+
origin.push(`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' END`);
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
selects.push(
|
|
9245
|
+
`json_patch('{}', json_object(${origin.join(", ")})) AS ${quoteIdent(ORIGIN_COLUMN)}`
|
|
9246
|
+
);
|
|
9247
|
+
selects.push(`r.${quoteIdent(SOURCES_COLUMN)} AS ${quoteIdent(SOURCES_COLUMN)}`);
|
|
9248
|
+
db.exec(
|
|
9249
|
+
`CREATE VIEW ${RESOLVED_VIEW} AS SELECT ${selects.join(", ")} FROM docs d LEFT JOIN ${DERIVED_ROWS} r USING (${quoteIdent("_path")})`
|
|
9250
|
+
);
|
|
9251
|
+
}
|
|
9252
|
+
async function deriveForTable(inputs, ctx, hint, fields) {
|
|
9253
|
+
const derive = ctx.config?.derive;
|
|
9254
|
+
const commands = commandsOf(derive);
|
|
9255
|
+
const result = await deriveMetadata(inputs, {
|
|
9256
|
+
cwd: ctx.cwd,
|
|
9257
|
+
base: ctx.base,
|
|
9258
|
+
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
9259
|
+
sources: derive?.sources ?? [...DERIVE_SOURCES],
|
|
9260
|
+
fields,
|
|
9261
|
+
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
9262
|
+
...commands !== void 0 ? { commands } : {},
|
|
9263
|
+
cache: ctx.cache,
|
|
9264
|
+
now: ctx.now ?? (() => /* @__PURE__ */ new Date())
|
|
9265
|
+
});
|
|
9266
|
+
assertSourcesAvailable(result.sources, hint);
|
|
9267
|
+
return result.records;
|
|
9268
|
+
}
|
|
9269
|
+
|
|
9270
|
+
// src/meta/commands/validate.ts
|
|
9271
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
9272
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
9273
|
+
import { resolve as resolve11, extname as extname3 } from "node:path";
|
|
9274
|
+
|
|
9275
|
+
// src/meta/core/baseline.ts
|
|
9276
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9277
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
9278
|
+
import { relative as relative9, resolve as resolve10 } from "node:path";
|
|
9279
|
+
var DEFAULT_BASELINE_PATH = ".manni-baseline.json";
|
|
9280
|
+
var LEGACY_BASELINE_PATH = ".docmeta-baseline.json";
|
|
9281
|
+
var BASELINE_VERSION = 1;
|
|
9282
|
+
var FINGERPRINT_RE = /^[0-9a-f]{16}$/;
|
|
9283
|
+
function canonicalFilePath(file, ctx) {
|
|
9284
|
+
if (!ctx) return file;
|
|
9285
|
+
const from = ctx.runBase ?? ctx.base;
|
|
9286
|
+
return relative9(ctx.base, resolve10(from, file)).replace(/\\/g, "/");
|
|
9287
|
+
}
|
|
9288
|
+
function canonicalSchemaRef(ref, ctx) {
|
|
9289
|
+
if (!ctx) return ref;
|
|
9290
|
+
if (classifyRef(ref).kind !== "file") return ref;
|
|
9291
|
+
const abs = resolve10(ctx.cwd, ref);
|
|
9292
|
+
return relative9(ctx.base, abs).replace(/\\/g, "/");
|
|
9293
|
+
}
|
|
9294
|
+
function fingerprint(e, ctx) {
|
|
9295
|
+
const parts = [
|
|
9296
|
+
canonicalSchemaRef(e.schema, ctx),
|
|
9297
|
+
e.instancePath,
|
|
9298
|
+
e.keyword,
|
|
9299
|
+
e.subject ?? ""
|
|
9300
|
+
];
|
|
9301
|
+
return createHash4("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
9302
|
+
}
|
|
9303
|
+
function serializeBaseline(baseline) {
|
|
9304
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
9305
|
+
for (const file of Object.keys(baseline.entries).sort()) {
|
|
9306
|
+
entries[file] = [...baseline.entries[file] ?? []].sort();
|
|
9307
|
+
}
|
|
9308
|
+
return `${JSON.stringify(
|
|
9309
|
+
{
|
|
9310
|
+
version: baseline.version,
|
|
9311
|
+
generatedWith: baseline.generatedWith,
|
|
9312
|
+
entries
|
|
9313
|
+
},
|
|
9314
|
+
null,
|
|
9315
|
+
2
|
|
9316
|
+
)}
|
|
9317
|
+
`;
|
|
9318
|
+
}
|
|
9319
|
+
function bad(source, detail) {
|
|
9320
|
+
throw new DocmetaError(`Baseline "${source}": ${detail}`);
|
|
9321
|
+
}
|
|
9322
|
+
function parseBaseline(text2, source) {
|
|
9323
|
+
let raw;
|
|
9324
|
+
try {
|
|
9325
|
+
raw = JSON.parse(stripBom(text2));
|
|
9326
|
+
} catch (err) {
|
|
9327
|
+
bad(source, `invalid JSON: ${errorMessage(err)}`);
|
|
9328
|
+
}
|
|
9329
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
9330
|
+
bad(source, "top level must be an object.");
|
|
9331
|
+
}
|
|
9332
|
+
const obj = raw;
|
|
9333
|
+
if (obj.version !== BASELINE_VERSION) {
|
|
9334
|
+
bad(
|
|
9335
|
+
source,
|
|
9336
|
+
`unsupported version ${JSON.stringify(obj.version)} (this manni writes version ${BASELINE_VERSION}). Re-record it with \`manni meta validate --write-baseline\`.`
|
|
9337
|
+
);
|
|
9338
|
+
}
|
|
9339
|
+
const generatedWith = typeof obj.generatedWith === "string" ? obj.generatedWith : "";
|
|
9340
|
+
const rawEntries = obj.entries;
|
|
9341
|
+
if (typeof rawEntries !== "object" || rawEntries === null || Array.isArray(rawEntries)) {
|
|
9342
|
+
bad(source, '"entries" must be an object mapping file paths to fingerprints.');
|
|
9343
|
+
}
|
|
9344
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
9345
|
+
for (const [file, value] of Object.entries(
|
|
9346
|
+
rawEntries
|
|
9347
|
+
)) {
|
|
9348
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
9349
|
+
bad(source, `entries["${file}"] must be a list of fingerprint strings.`);
|
|
9350
|
+
}
|
|
9351
|
+
const malformed = value.find((v) => !FINGERPRINT_RE.test(v));
|
|
9352
|
+
if (malformed !== void 0) {
|
|
9353
|
+
bad(
|
|
9354
|
+
source,
|
|
9355
|
+
`entries["${file}"] contains ${JSON.stringify(malformed)}, which is not a fingerprint (16 lowercase hex characters).`
|
|
9356
|
+
);
|
|
9357
|
+
}
|
|
9358
|
+
entries[file] = value;
|
|
9359
|
+
}
|
|
9360
|
+
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
9361
|
+
}
|
|
9362
|
+
async function readBaseline(absPath, source) {
|
|
9363
|
+
let text2;
|
|
9364
|
+
try {
|
|
9365
|
+
text2 = await readFile6(absPath, "utf8");
|
|
9366
|
+
} catch (err) {
|
|
9367
|
+
if (err.code === "ENOENT") return null;
|
|
9368
|
+
throw new DocmetaError(
|
|
9369
|
+
`Baseline "${source}" could not be read: ${errorMessage(err)}`
|
|
9370
|
+
);
|
|
9371
|
+
}
|
|
9372
|
+
return parseBaseline(text2, source);
|
|
9373
|
+
}
|
|
9374
|
+
async function writeBaselineFile(absPath, baseline, source) {
|
|
9375
|
+
try {
|
|
9376
|
+
await writeFileAtomic(absPath, serializeBaseline(baseline));
|
|
9377
|
+
} catch (err) {
|
|
9378
|
+
throw new DocmetaError(
|
|
9379
|
+
`Baseline "${source}" could not be written: ${errorMessage(err)}`
|
|
9380
|
+
);
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
7474
9383
|
function buildBaseline(results, generatedWith, ctx) {
|
|
7475
9384
|
const entries = /* @__PURE__ */ Object.create(null);
|
|
7476
9385
|
for (const r of results) {
|
|
@@ -7586,8 +9495,8 @@ function synthesizeMessage(extras, row) {
|
|
|
7586
9495
|
const pairs = extras.flatMap((c) => {
|
|
7587
9496
|
const value = row[c];
|
|
7588
9497
|
if (value === null || value === void 0) return [];
|
|
7589
|
-
const
|
|
7590
|
-
return [`${c}=${
|
|
9498
|
+
const text2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : JSON.stringify(value);
|
|
9499
|
+
return [`${c}=${text2}`];
|
|
7591
9500
|
});
|
|
7592
9501
|
return pairs.length > 0 ? pairs.join(", ") : "check matched";
|
|
7593
9502
|
}
|
|
@@ -7597,9 +9506,26 @@ async function runChecks(checks, entries, ctx = {}) {
|
|
|
7597
9506
|
const { DatabaseSync } = await loadSqlite();
|
|
7598
9507
|
const db = new DatabaseSync(":memory:");
|
|
7599
9508
|
try {
|
|
7600
|
-
|
|
9509
|
+
const dataColumns = corpusDataColumns(entries);
|
|
9510
|
+
createDocsTable(db, entries, dataColumns);
|
|
7601
9511
|
registerLineFor(db, entries);
|
|
7602
9512
|
createCollectionViews(db, collectCollections(entries, ctx));
|
|
9513
|
+
const wantsDerived = checks.filter(
|
|
9514
|
+
(c) => mentionsDerived(c.query) || mentionsResolved(c.query)
|
|
9515
|
+
);
|
|
9516
|
+
if (wantsDerived.length > 0) {
|
|
9517
|
+
if (ctx.derive === void 0) {
|
|
9518
|
+
throw new DocmetaError(
|
|
9519
|
+
`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.`
|
|
9520
|
+
);
|
|
9521
|
+
}
|
|
9522
|
+
const commands = ctx.commands;
|
|
9523
|
+
const readable = derivableFields(commands);
|
|
9524
|
+
const fields = /* @__PURE__ */ new Set();
|
|
9525
|
+
for (const c of wantsDerived) for (const f of fieldsForSql(c.query, readable)) fields.add(f);
|
|
9526
|
+
createDerivedView(db, await ctx.derive([...fields]), derivedColumns(commands));
|
|
9527
|
+
createResolvedView(db, dataColumns, commands);
|
|
9528
|
+
}
|
|
7603
9529
|
db.exec("PRAGMA query_only = 1");
|
|
7604
9530
|
const loaded = new Set(entries.map((e) => e.label));
|
|
7605
9531
|
for (const check of checks) {
|
|
@@ -7609,17 +9535,17 @@ async function runChecks(checks, entries, ctx = {}) {
|
|
|
7609
9535
|
`check "${check.name}": ${head.split(/\s/, 1)[0]?.toUpperCase() ?? "that statement"} is refused: it can write outside the docs table.`
|
|
7610
9536
|
);
|
|
7611
9537
|
}
|
|
7612
|
-
let
|
|
9538
|
+
let tokens2;
|
|
7613
9539
|
try {
|
|
7614
|
-
|
|
9540
|
+
tokens2 = collectNamedParameters(check.query);
|
|
7615
9541
|
} catch (err) {
|
|
7616
9542
|
throw new DocmetaError(
|
|
7617
9543
|
`check "${check.name}": ${errorMessage(err)}`
|
|
7618
9544
|
);
|
|
7619
9545
|
}
|
|
7620
|
-
if (
|
|
9546
|
+
if (tokens2.length > 0) {
|
|
7621
9547
|
throw new DocmetaError(
|
|
7622
|
-
`check "${check.name}": the SQL references ${
|
|
9548
|
+
`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.`
|
|
7623
9549
|
);
|
|
7624
9550
|
}
|
|
7625
9551
|
let columns;
|
|
@@ -7664,17 +9590,17 @@ function parseErrorResult(file, format, message, keyword) {
|
|
|
7664
9590
|
}
|
|
7665
9591
|
function resolveBaselineRequest(opts, configured, configDir, cwd) {
|
|
7666
9592
|
const named = (label) => ({
|
|
7667
|
-
absPath:
|
|
9593
|
+
absPath: resolve11(cwd, label),
|
|
7668
9594
|
label
|
|
7669
9595
|
});
|
|
7670
9596
|
const implied = () => {
|
|
7671
9597
|
const base = configDir ?? cwd;
|
|
7672
9598
|
if (configured !== void 0) {
|
|
7673
|
-
return { absPath:
|
|
9599
|
+
return { absPath: resolve11(base, configured), label: configured };
|
|
7674
9600
|
}
|
|
7675
|
-
const current =
|
|
7676
|
-
const legacy =
|
|
7677
|
-
if (!
|
|
9601
|
+
const current = resolve11(base, DEFAULT_BASELINE_PATH);
|
|
9602
|
+
const legacy = resolve11(base, LEGACY_BASELINE_PATH);
|
|
9603
|
+
if (!existsSync3(current) && existsSync3(legacy)) {
|
|
7678
9604
|
warn(
|
|
7679
9605
|
`"${LEGACY_BASELINE_PATH}" is the pre-rename baseline file name. Rename it to "${DEFAULT_BASELINE_PATH}".`
|
|
7680
9606
|
);
|
|
@@ -7693,6 +9619,13 @@ function resolveBaselineRequest(opts, configured, configDir, cwd) {
|
|
|
7693
9619
|
if (configured) return { ...implied(), write: false };
|
|
7694
9620
|
return null;
|
|
7695
9621
|
}
|
|
9622
|
+
function once(fn) {
|
|
9623
|
+
let pending;
|
|
9624
|
+
return () => {
|
|
9625
|
+
pending ??= fn();
|
|
9626
|
+
return pending;
|
|
9627
|
+
};
|
|
9628
|
+
}
|
|
7696
9629
|
async function runValidate(opts) {
|
|
7697
9630
|
const cwd = opts.cwd ?? process.cwd();
|
|
7698
9631
|
const {
|
|
@@ -7785,6 +9718,39 @@ async function runValidate(opts) {
|
|
|
7785
9718
|
}
|
|
7786
9719
|
const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
|
|
7787
9720
|
const checkEntries = [];
|
|
9721
|
+
const deriveConfig = config?.derive;
|
|
9722
|
+
const deriveWillRun = deriveConfig !== void 0 && deriveConfig.fields.length > 0 && opts.derive !== false;
|
|
9723
|
+
const derivedChecks = checksWillRun ? configuredChecks.filter((c) => mentionsDerived(c.query) || mentionsResolved(c.query)) : [];
|
|
9724
|
+
const checksNeedDerived = derivedChecks.length > 0;
|
|
9725
|
+
const deriveInputs = [];
|
|
9726
|
+
const keepDeriveInputs = deriveWillRun || checksNeedDerived;
|
|
9727
|
+
const deriveCommands = commandsOf(deriveConfig);
|
|
9728
|
+
const deriveFields = new Set(deriveWillRun ? deriveConfig.fields : []);
|
|
9729
|
+
const readable = derivableFields(deriveCommands);
|
|
9730
|
+
for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
|
|
9731
|
+
const derivedOnce = once(async () => {
|
|
9732
|
+
const derived = await deriveMetadata(deriveInputs, {
|
|
9733
|
+
cwd,
|
|
9734
|
+
base,
|
|
9735
|
+
...configDir !== void 0 ? { configDir } : {},
|
|
9736
|
+
sources: deriveConfig?.sources ?? [...DERIVE_SOURCES],
|
|
9737
|
+
fields: [...deriveFields],
|
|
9738
|
+
...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
|
|
9739
|
+
...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
|
|
9740
|
+
cache: opts.cache ?? true,
|
|
9741
|
+
now: opts.now ?? (() => /* @__PURE__ */ new Date())
|
|
9742
|
+
});
|
|
9743
|
+
assertSourcesAvailable(
|
|
9744
|
+
derived.sources,
|
|
9745
|
+
deriveWillRun ? "pass --no-derive to skip the comparison, or narrow derive.sources" : "narrow derive.sources in manni.config.yaml"
|
|
9746
|
+
);
|
|
9747
|
+
for (const [name2, status] of Object.entries(derived.sources)) {
|
|
9748
|
+
if (status.available && status.reason !== void 0) {
|
|
9749
|
+
opts.onNotice?.(`${name2}: ${status.reason}`);
|
|
9750
|
+
}
|
|
9751
|
+
}
|
|
9752
|
+
return derived;
|
|
9753
|
+
});
|
|
7788
9754
|
const processOne = async (label, content, extension) => {
|
|
7789
9755
|
const extractor = forcedExtractor ?? extractorForExtension(extension);
|
|
7790
9756
|
if (!extractor) {
|
|
@@ -7805,6 +9771,9 @@ async function runValidate(opts) {
|
|
|
7805
9771
|
);
|
|
7806
9772
|
return;
|
|
7807
9773
|
}
|
|
9774
|
+
if (keepDeriveInputs && label !== STDIN_LABEL) {
|
|
9775
|
+
deriveInputs.push({ label, absPath: resolve11(base, label), content, extracted });
|
|
9776
|
+
}
|
|
7808
9777
|
const merged = mergeExternalMetadata(label, extracted, externalMetadata, members, base);
|
|
7809
9778
|
extracted = merged.extracted;
|
|
7810
9779
|
for (const j of merged.joins) {
|
|
@@ -7885,8 +9854,8 @@ async function runValidate(opts) {
|
|
|
7885
9854
|
await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
|
|
7886
9855
|
}
|
|
7887
9856
|
for (const file of files) {
|
|
7888
|
-
const content = await
|
|
7889
|
-
await processOne(file, content,
|
|
9857
|
+
const content = await readFile7(resolve11(base, file), "utf8");
|
|
9858
|
+
await processOne(file, content, extname3(file));
|
|
7890
9859
|
}
|
|
7891
9860
|
if (externalMetadata) {
|
|
7892
9861
|
const byLabel = new Map(results.map((r) => [r.file, r]));
|
|
@@ -7930,7 +9899,16 @@ async function runValidate(opts) {
|
|
|
7930
9899
|
// corpus see the same collections.
|
|
7931
9900
|
collections: declaredCollections,
|
|
7932
9901
|
...configDir !== void 0 ? { configDir } : {},
|
|
7933
|
-
base
|
|
9902
|
+
base,
|
|
9903
|
+
// A check that names the `derived` table (0040) gets the same view
|
|
9904
|
+
// `query` builds, from the run's one derivation — which already
|
|
9905
|
+
// covers every field the checks can read, so the list handed back
|
|
9906
|
+
// here is not needed. Built only when asked for, because building
|
|
9907
|
+
// it spawns git.
|
|
9908
|
+
derive: async () => (await derivedOnce()).records,
|
|
9909
|
+
// Its columns include the configured commands' keys (0042), which a
|
|
9910
|
+
// check's context cannot read from a config since 0041.
|
|
9911
|
+
...deriveCommands !== void 0 ? { commands: deriveCommands } : {}
|
|
7934
9912
|
});
|
|
7935
9913
|
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7936
9914
|
for (const [file, errs] of findings) {
|
|
@@ -7945,6 +9923,24 @@ async function runValidate(opts) {
|
|
|
7945
9923
|
}
|
|
7946
9924
|
}
|
|
7947
9925
|
}
|
|
9926
|
+
if (deriveWillRun && deriveInputs.length > 0) {
|
|
9927
|
+
const derived = await derivedOnce();
|
|
9928
|
+
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
9929
|
+
for (const input of deriveInputs) {
|
|
9930
|
+
const record = derived.records.get(input.label);
|
|
9931
|
+
const result = byFile.get(input.label);
|
|
9932
|
+
if (!record || !result) continue;
|
|
9933
|
+
const findings = staleFindings(
|
|
9934
|
+
deriveConfig.fields.map(
|
|
9935
|
+
(field) => compareDerived(field, input.extracted.data[field], record.fields[field])
|
|
9936
|
+
),
|
|
9937
|
+
input.extracted.lineFor
|
|
9938
|
+
);
|
|
9939
|
+
if (findings.length === 0) continue;
|
|
9940
|
+
result.errors.push(...findings);
|
|
9941
|
+
result.ok = false;
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
7948
9944
|
const frame = { cwd, base: configDir ?? cwd, runBase: base };
|
|
7949
9945
|
const { results: reported, baseline } = await settleBaseline(
|
|
7950
9946
|
results,
|
|
@@ -8005,8 +10001,8 @@ async function settleBaseline(results, request, ctx) {
|
|
|
8005
10001
|
}
|
|
8006
10002
|
|
|
8007
10003
|
// src/meta/commands/get.ts
|
|
8008
|
-
import { readFile as
|
|
8009
|
-
import { resolve as
|
|
10004
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
10005
|
+
import { resolve as resolve12, extname as extname4 } from "node:path";
|
|
8010
10006
|
async function runGet(opts) {
|
|
8011
10007
|
const cwd = opts.cwd ?? process.cwd();
|
|
8012
10008
|
if (opts.fields.length === 0) {
|
|
@@ -8066,6 +10062,11 @@ async function runGet(opts) {
|
|
|
8066
10062
|
base,
|
|
8067
10063
|
offline: opts.offline ?? config?.offline ?? false
|
|
8068
10064
|
});
|
|
10065
|
+
const commands = commandsOf(config?.derive) ?? {};
|
|
10066
|
+
const deriving = opts.derived !== false;
|
|
10067
|
+
const derivableFields2 = deriving ? opts.fields.filter((f) => isBuiltinField(f) || Object.hasOwn(commands, f)) : [];
|
|
10068
|
+
const deriveInputs = [];
|
|
10069
|
+
const carried = /* @__PURE__ */ new Map();
|
|
8069
10070
|
const readOne = (label, content, extension) => {
|
|
8070
10071
|
const extractor = forced ?? extractorForExtension(extension);
|
|
8071
10072
|
if (!extractor) {
|
|
@@ -8076,15 +10077,18 @@ async function runGet(opts) {
|
|
|
8076
10077
|
const members = membersFor(label);
|
|
8077
10078
|
let extracted;
|
|
8078
10079
|
try {
|
|
8079
|
-
|
|
8080
|
-
label,
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
10080
|
+
const own2 = extractor.extract(content, label, {
|
|
10081
|
+
elements: resolveElements(label, config, members)
|
|
10082
|
+
});
|
|
10083
|
+
if (deriving && label !== STDIN_LABEL) {
|
|
10084
|
+
deriveInputs.push({
|
|
10085
|
+
label,
|
|
10086
|
+
absPath: resolve12(base, label),
|
|
10087
|
+
content,
|
|
10088
|
+
extracted: own2
|
|
10089
|
+
});
|
|
10090
|
+
}
|
|
10091
|
+
extracted = mergeExternalMetadata(label, own2, externalMetadata, members, base).extracted;
|
|
8088
10092
|
} catch (err) {
|
|
8089
10093
|
if (err instanceof DocmetaError) throw err;
|
|
8090
10094
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -8092,7 +10096,12 @@ async function runGet(opts) {
|
|
|
8092
10096
|
return;
|
|
8093
10097
|
}
|
|
8094
10098
|
const values = {};
|
|
8095
|
-
|
|
10099
|
+
const carries = {};
|
|
10100
|
+
for (const f of opts.fields) {
|
|
10101
|
+
values[f] = resolveField(extracted.data, f);
|
|
10102
|
+
carries[f] = carriesField(extracted.data, f);
|
|
10103
|
+
}
|
|
10104
|
+
carried.set(label, carries);
|
|
8096
10105
|
out.push({ file: label, present: extracted.present, values });
|
|
8097
10106
|
};
|
|
8098
10107
|
if (usingStdin) {
|
|
@@ -8101,14 +10110,78 @@ async function runGet(opts) {
|
|
|
8101
10110
|
"Reading from stdin (`-`) requires --as <format> to choose an extractor."
|
|
8102
10111
|
);
|
|
8103
10112
|
}
|
|
8104
|
-
readOne(
|
|
10113
|
+
readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
8105
10114
|
}
|
|
8106
10115
|
for (const file of files) {
|
|
8107
|
-
const content = await
|
|
8108
|
-
readOne(file, content,
|
|
10116
|
+
const content = await readFile8(resolve12(base, file), "utf8");
|
|
10117
|
+
readOne(file, content, extname4(file));
|
|
10118
|
+
}
|
|
10119
|
+
if (deriving) {
|
|
10120
|
+
await attachResolved(out, deriveInputs, {
|
|
10121
|
+
fields: opts.fields,
|
|
10122
|
+
derivable: derivableFields2,
|
|
10123
|
+
carried,
|
|
10124
|
+
run: {
|
|
10125
|
+
cwd,
|
|
10126
|
+
base,
|
|
10127
|
+
configDir,
|
|
10128
|
+
config,
|
|
10129
|
+
cache: opts.cache ?? true,
|
|
10130
|
+
now: opts.now ?? (() => /* @__PURE__ */ new Date())
|
|
10131
|
+
}
|
|
10132
|
+
});
|
|
8109
10133
|
}
|
|
8110
10134
|
return out;
|
|
8111
10135
|
}
|
|
10136
|
+
async function attachResolved(out, inputs, opts) {
|
|
10137
|
+
const { run: run2 } = opts;
|
|
10138
|
+
const derive = run2.config?.derive;
|
|
10139
|
+
const commands = commandsOf(derive);
|
|
10140
|
+
const records = opts.derivable.length === 0 || inputs.length === 0 ? /* @__PURE__ */ new Map() : await (async () => {
|
|
10141
|
+
const result = await deriveMetadata(inputs, {
|
|
10142
|
+
cwd: run2.cwd,
|
|
10143
|
+
base: run2.base,
|
|
10144
|
+
...run2.configDir !== void 0 ? { configDir: run2.configDir } : {},
|
|
10145
|
+
sources: derive?.sources ?? [...DERIVE_SOURCES],
|
|
10146
|
+
fields: opts.derivable,
|
|
10147
|
+
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
10148
|
+
...commands !== void 0 ? { commands } : {},
|
|
10149
|
+
cache: run2.cache,
|
|
10150
|
+
now: run2.now
|
|
10151
|
+
});
|
|
10152
|
+
assertSourcesAvailable(
|
|
10153
|
+
result.sources,
|
|
10154
|
+
"pass --no-derived, or narrow derive.sources"
|
|
10155
|
+
);
|
|
10156
|
+
return result.records;
|
|
10157
|
+
})();
|
|
10158
|
+
for (const r of out) {
|
|
10159
|
+
if (r.error !== void 0) continue;
|
|
10160
|
+
const record = records.get(r.file);
|
|
10161
|
+
const derived = {};
|
|
10162
|
+
for (const field of opts.derivable) {
|
|
10163
|
+
derived[field] = record?.fields[field] ?? null;
|
|
10164
|
+
}
|
|
10165
|
+
const carries = opts.carried.get(r.file) ?? {};
|
|
10166
|
+
const resolved = {};
|
|
10167
|
+
const origin = {};
|
|
10168
|
+
for (const field of opts.fields) {
|
|
10169
|
+
const evidence = derived[field];
|
|
10170
|
+
if (carries[field] === true) {
|
|
10171
|
+
resolved[field] = r.values[field];
|
|
10172
|
+
origin[field] = "asserted";
|
|
10173
|
+
} else if (evidence != null) {
|
|
10174
|
+
resolved[field] = evidence.value;
|
|
10175
|
+
origin[field] = "derived";
|
|
10176
|
+
} else {
|
|
10177
|
+
resolved[field] = void 0;
|
|
10178
|
+
}
|
|
10179
|
+
}
|
|
10180
|
+
if (opts.derivable.length > 0) r.derived = derived;
|
|
10181
|
+
r.resolved = resolved;
|
|
10182
|
+
r.origin = origin;
|
|
10183
|
+
}
|
|
10184
|
+
}
|
|
8112
10185
|
function resolveField(data, field) {
|
|
8113
10186
|
const segments = field.startsWith("/") ? parseJsonPointer(field) : field.split(".");
|
|
8114
10187
|
const descended = descend(data, segments);
|
|
@@ -8118,6 +10191,23 @@ function resolveField(data, field) {
|
|
|
8118
10191
|
}
|
|
8119
10192
|
return void 0;
|
|
8120
10193
|
}
|
|
10194
|
+
function carriesField(data, field) {
|
|
10195
|
+
const segments = field.startsWith("/") ? parseJsonPointer(field) : field.split(".");
|
|
10196
|
+
if (carriesPath(data, segments)) return true;
|
|
10197
|
+
return !field.startsWith("/") && field.includes(".") && Object.prototype.hasOwnProperty.call(data, field);
|
|
10198
|
+
}
|
|
10199
|
+
function carriesPath(data, segments) {
|
|
10200
|
+
const last = segments.at(-1);
|
|
10201
|
+
if (last === void 0) return true;
|
|
10202
|
+
const parent = segments.length === 1 ? data : descend(data, segments.slice(0, -1));
|
|
10203
|
+
if (Array.isArray(parent)) {
|
|
10204
|
+
return /^\d+$/.test(last) && Number(last) < parent.length;
|
|
10205
|
+
}
|
|
10206
|
+
if (parent !== null && typeof parent === "object") {
|
|
10207
|
+
return Object.prototype.hasOwnProperty.call(parent, last);
|
|
10208
|
+
}
|
|
10209
|
+
return false;
|
|
10210
|
+
}
|
|
8121
10211
|
function descend(data, segments) {
|
|
8122
10212
|
let current = data;
|
|
8123
10213
|
for (const segment of segments) {
|
|
@@ -8140,9 +10230,9 @@ function parseJsonPointer(pointer) {
|
|
|
8140
10230
|
}
|
|
8141
10231
|
|
|
8142
10232
|
// src/meta/commands/schemas.ts
|
|
8143
|
-
import { existsSync as
|
|
8144
|
-
import { mkdir as mkdir2, readFile as
|
|
8145
|
-
import { dirname as
|
|
10233
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
10234
|
+
import { mkdir as mkdir2, readFile as readFile9 } from "node:fs/promises";
|
|
10235
|
+
import { dirname as dirname8, extname as extname5, isAbsolute as isAbsolute4, join as join6, relative as relative10, resolve as resolve13, sep as sep6 } from "node:path";
|
|
8146
10236
|
import { parseDocument as parseDocument4, parse as parseYaml3, stringify } from "yaml";
|
|
8147
10237
|
function getSchemasInfo() {
|
|
8148
10238
|
return { builtins: listBuiltins(), formats: listFormats() };
|
|
@@ -8150,7 +10240,7 @@ function getSchemasInfo() {
|
|
|
8150
10240
|
var DEFAULT_VENDOR_DIR = "./schema";
|
|
8151
10241
|
var DEFAULT_CONFIG_NAME = "manni.config.yaml";
|
|
8152
10242
|
function posixRelative(from, to) {
|
|
8153
|
-
return
|
|
10243
|
+
return relative10(from, to).split(sep6).join("/");
|
|
8154
10244
|
}
|
|
8155
10245
|
function vendorFileName(url) {
|
|
8156
10246
|
let parsed2;
|
|
@@ -8175,20 +10265,20 @@ function vendorFileName(url) {
|
|
|
8175
10265
|
if (!name2.toLowerCase().endsWith(".json")) name2 += ".json";
|
|
8176
10266
|
return name2;
|
|
8177
10267
|
}
|
|
8178
|
-
async function assertNotIgnored(absFile, absDir, cwd, onNotice,
|
|
10268
|
+
async function assertNotIgnored(absFile, absDir, cwd, onNotice, text2 = VENDOR_IGNORE_TEXT) {
|
|
8179
10269
|
const relFile = posixRelative(cwd, absFile);
|
|
8180
10270
|
const relDir = posixRelative(cwd, absDir);
|
|
8181
10271
|
const candidates = [relFile];
|
|
8182
10272
|
if (relDir !== "" && !relDir.startsWith("..")) candidates.push(relDir);
|
|
8183
10273
|
const answer = await gitIgnored(candidates, cwd);
|
|
8184
10274
|
if (!answer.available) {
|
|
8185
|
-
onNotice?.(
|
|
10275
|
+
onNotice?.(text2.unchecked(relDir || "."));
|
|
8186
10276
|
return;
|
|
8187
10277
|
}
|
|
8188
10278
|
const ignoredDir = answer.ignored.has(relDir);
|
|
8189
10279
|
const ignoredFile = answer.ignored.has(relFile);
|
|
8190
10280
|
if (!ignoredDir && !ignoredFile) return;
|
|
8191
|
-
throw new DocmetaError(
|
|
10281
|
+
throw new DocmetaError(text2.refusal(ignoredDir ? relDir : relFile));
|
|
8192
10282
|
}
|
|
8193
10283
|
var VENDOR_IGNORE_TEXT = {
|
|
8194
10284
|
refusal: (target) => `Refusing to vendor into "${target}": git reports it as ignored. A vendored schema has to be committed \u2014 an ignored copy validates on this machine and is simply missing in CI, where the failure reads as a schema nobody changed. Vendor into a directory your repository tracks (\`--dir\`, default \`${DEFAULT_VENDOR_DIR}\`), or drop the .gitignore rule covering this path.`,
|
|
@@ -8219,12 +10309,12 @@ function renderConfig(existing, entries, section) {
|
|
|
8219
10309
|
doc.setIn(section === void 0 ? ["schemas"] : [section, "schemas"], entries);
|
|
8220
10310
|
return doc.toString();
|
|
8221
10311
|
}
|
|
8222
|
-
function reparseConfig(
|
|
10312
|
+
function reparseConfig(text2, source, section) {
|
|
8223
10313
|
if (section === void 0) {
|
|
8224
|
-
parseConfig(
|
|
10314
|
+
parseConfig(text2, source);
|
|
8225
10315
|
return;
|
|
8226
10316
|
}
|
|
8227
|
-
const doc = parseYaml3(
|
|
10317
|
+
const doc = parseYaml3(text2);
|
|
8228
10318
|
parseConfigValue(doc[section], source, section);
|
|
8229
10319
|
}
|
|
8230
10320
|
async function runVendorSchema(opts) {
|
|
@@ -8236,10 +10326,10 @@ async function runVendorSchema(opts) {
|
|
|
8236
10326
|
);
|
|
8237
10327
|
}
|
|
8238
10328
|
const loaded = await loadConfig(opts.configPath, cwd);
|
|
8239
|
-
const configPath = loaded?.path ??
|
|
10329
|
+
const configPath = loaded?.path ?? resolve13(cwd, DEFAULT_CONFIG_NAME);
|
|
8240
10330
|
const configDir = loaded?.dir ?? cwd;
|
|
8241
|
-
const absDir =
|
|
8242
|
-
const absFile =
|
|
10331
|
+
const absDir = resolve13(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
|
|
10332
|
+
const absFile = join6(absDir, vendorFileName(url));
|
|
8243
10333
|
await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
|
|
8244
10334
|
const fromConfig = posixRelative(configDir, absFile);
|
|
8245
10335
|
const ref = isAbsolute4(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
|
|
@@ -8256,8 +10346,8 @@ async function runVendorSchema(opts) {
|
|
|
8256
10346
|
);
|
|
8257
10347
|
}
|
|
8258
10348
|
let unchanged = false;
|
|
8259
|
-
if (
|
|
8260
|
-
const current = await
|
|
10349
|
+
if (existsSync4(absFile)) {
|
|
10350
|
+
const current = await readFile9(absFile);
|
|
8261
10351
|
unchanged = bytes.equals(current);
|
|
8262
10352
|
if (!unchanged && !folded.replaced) {
|
|
8263
10353
|
throw new DocmetaError(
|
|
@@ -8267,11 +10357,11 @@ async function runVendorSchema(opts) {
|
|
|
8267
10357
|
}
|
|
8268
10358
|
await mkdir2(absDir, { recursive: true });
|
|
8269
10359
|
await writeFileAtomic(absFile, bytes);
|
|
8270
|
-
const existing = loaded ? await
|
|
10360
|
+
const existing = loaded ? await readFile9(configPath, "utf8") : null;
|
|
8271
10361
|
const section = loaded ? loaded.section : META_SECTION;
|
|
8272
|
-
const
|
|
8273
|
-
reparseConfig(
|
|
8274
|
-
await writeFileAtomic(configPath,
|
|
10362
|
+
const text2 = renderConfig(existing, folded.entries, section);
|
|
10363
|
+
reparseConfig(text2, posixRelative(cwd, configPath) || DEFAULT_CONFIG_NAME, section);
|
|
10364
|
+
await writeFileAtomic(configPath, text2);
|
|
8275
10365
|
return {
|
|
8276
10366
|
url,
|
|
8277
10367
|
file: posixRelative(cwd, absFile),
|
|
@@ -8418,15 +10508,15 @@ async function runInferSchema(opts) {
|
|
|
8418
10508
|
}
|
|
8419
10509
|
let absOut;
|
|
8420
10510
|
if (opts.out !== void 0) {
|
|
8421
|
-
absOut =
|
|
10511
|
+
absOut = resolve13(cwd, opts.out);
|
|
8422
10512
|
await assertNotIgnored(
|
|
8423
10513
|
absOut,
|
|
8424
|
-
|
|
10514
|
+
dirname8(absOut),
|
|
8425
10515
|
cwd,
|
|
8426
10516
|
opts.onNotice,
|
|
8427
10517
|
DRAFT_IGNORE_TEXT
|
|
8428
10518
|
);
|
|
8429
|
-
if (
|
|
10519
|
+
if (existsSync4(absOut)) {
|
|
8430
10520
|
throw new DocmetaError(
|
|
8431
10521
|
`"${posixRelative(cwd, absOut) || opts.out}" already exists, and \`schemas infer\` will not overwrite it. The draft is a starting point you then edit, so clobbering it would throw away the edits. Pass a different --out path, or remove that file first.`
|
|
8432
10522
|
);
|
|
@@ -8505,8 +10595,8 @@ async function runInferSchema(opts) {
|
|
|
8505
10595
|
scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
8506
10596
|
}
|
|
8507
10597
|
for (const file of files) {
|
|
8508
|
-
const content = await
|
|
8509
|
-
scanOne(file, content,
|
|
10598
|
+
const content = await readFile9(resolve13(base, file), "utf8");
|
|
10599
|
+
scanOne(file, content, extname5(file));
|
|
8510
10600
|
}
|
|
8511
10601
|
for (const u of unreadable) {
|
|
8512
10602
|
opts.onNotice?.(`could not read metadata from "${u.file}": ${u.message}`);
|
|
@@ -8547,7 +10637,7 @@ async function runInferSchema(opts) {
|
|
|
8547
10637
|
};
|
|
8548
10638
|
const keys = kept.map(({ sawEmptyString: _drop, ...rest }) => rest);
|
|
8549
10639
|
if (absOut !== void 0) {
|
|
8550
|
-
await mkdir2(
|
|
10640
|
+
await mkdir2(dirname8(absOut), { recursive: true });
|
|
8551
10641
|
await writeFileAtomic(absOut, `${JSON.stringify(draft, null, 2)}
|
|
8552
10642
|
`);
|
|
8553
10643
|
}
|
|
@@ -8565,9 +10655,9 @@ async function runInferSchema(opts) {
|
|
|
8565
10655
|
|
|
8566
10656
|
// src/meta/commands/query.ts
|
|
8567
10657
|
import { randomBytes } from "node:crypto";
|
|
8568
|
-
import { existsSync as
|
|
8569
|
-
import { mkdir as mkdir3, open, readFile as
|
|
8570
|
-
import { dirname as
|
|
10658
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
10659
|
+
import { mkdir as mkdir3, open, readFile as readFile10, rename as rename2, rm as rm2 } from "node:fs/promises";
|
|
10660
|
+
import { dirname as dirname9, isAbsolute as isAbsolute5, resolve as resolve14, extname as extname6, sep as sep7 } from "node:path";
|
|
8571
10661
|
import { parseDocument as parseDocument5, isMap as isMap4, isSeq as isSeq2, isScalar as isScalar3 } from "yaml";
|
|
8572
10662
|
async function runQuery(opts) {
|
|
8573
10663
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -8672,16 +10762,11 @@ async function runQuery(opts) {
|
|
|
8672
10762
|
);
|
|
8673
10763
|
}
|
|
8674
10764
|
const members = membersFor(label);
|
|
8675
|
-
const
|
|
8676
|
-
label,
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
externalMetadata,
|
|
8681
|
-
members,
|
|
8682
|
-
base
|
|
8683
|
-
).extracted;
|
|
8684
|
-
entries.push({ label, extracted, extractor });
|
|
10765
|
+
const own2 = extractor.extract(content, label, {
|
|
10766
|
+
elements: resolveElements(label, config, members)
|
|
10767
|
+
});
|
|
10768
|
+
const extracted = mergeExternalMetadata(label, own2, externalMetadata, members, base).extracted;
|
|
10769
|
+
entries.push({ label, extracted, extractor, own: own2, content });
|
|
8685
10770
|
};
|
|
8686
10771
|
if (usingStdin) {
|
|
8687
10772
|
if (!forced) {
|
|
@@ -8698,15 +10783,17 @@ async function runQuery(opts) {
|
|
|
8698
10783
|
readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
|
|
8699
10784
|
}
|
|
8700
10785
|
for (const file of files) {
|
|
8701
|
-
const content = await
|
|
8702
|
-
readOne(file, content,
|
|
10786
|
+
const content = await readFile10(resolve14(base, file), "utf8");
|
|
10787
|
+
readOne(file, content, extname6(file));
|
|
8703
10788
|
}
|
|
8704
|
-
const db = opts.db === void 0 ? void 0 : { resolved:
|
|
8705
|
-
const
|
|
10789
|
+
const db = opts.db === void 0 ? void 0 : { resolved: resolve14(cwd, opts.db), display: opts.db };
|
|
10790
|
+
const run2 = await runSql(sql, entries, {
|
|
8706
10791
|
target: db,
|
|
8707
10792
|
params: boundParams,
|
|
8708
10793
|
...opts.schemas !== void 0 && opts.schemas.length > 0 ? { cliSchemas: opts.schemas } : {},
|
|
8709
10794
|
write: !opts.dryRun,
|
|
10795
|
+
cache: opts.cache ?? true,
|
|
10796
|
+
...opts.now !== void 0 ? { now: opts.now } : {},
|
|
8710
10797
|
base,
|
|
8711
10798
|
config,
|
|
8712
10799
|
cwd,
|
|
@@ -8718,9 +10805,10 @@ async function runQuery(opts) {
|
|
|
8718
10805
|
collections,
|
|
8719
10806
|
declaredCollections,
|
|
8720
10807
|
memberships: membersFor,
|
|
10808
|
+
managed: new Set(config?.derive?.fields ?? []),
|
|
8721
10809
|
onNotice: opts.onNotice
|
|
8722
10810
|
});
|
|
8723
|
-
return { ...
|
|
10811
|
+
return { ...run2, frame: { cwd, base: configDir ?? cwd, runBase: base } };
|
|
8724
10812
|
}
|
|
8725
10813
|
function bindParams(params) {
|
|
8726
10814
|
const out = /* @__PURE__ */ Object.create(null);
|
|
@@ -8759,6 +10847,8 @@ async function prepareDbTarget(resolved, display) {
|
|
|
8759
10847
|
}
|
|
8760
10848
|
var MissingCollectionView = class extends Error {
|
|
8761
10849
|
};
|
|
10850
|
+
var MissingDerivedTable = class extends Error {
|
|
10851
|
+
};
|
|
8762
10852
|
function refuseSqlError(err) {
|
|
8763
10853
|
const message = errorMessage(err);
|
|
8764
10854
|
if (message.includes("UNIQUE constraint failed: docs._path")) {
|
|
@@ -8767,8 +10857,18 @@ function refuseSqlError(err) {
|
|
|
8767
10857
|
const viewWrite = /cannot modify (.+?) because it is a view/.exec(message);
|
|
8768
10858
|
const viewName = viewWrite?.[1];
|
|
8769
10859
|
if (viewName !== void 0) {
|
|
10860
|
+
if (viewName.toLowerCase() === DERIVED_VIEW) {
|
|
10861
|
+
throw new DocmetaError(
|
|
10862
|
+
`SQL error: ${message}; a collection or the derived table is read-only \u2014 the derived table is recomputed from the evidence on every run. Change the evidence, or stamp it into the documents with manni meta derive.`
|
|
10863
|
+
);
|
|
10864
|
+
}
|
|
10865
|
+
if (viewName.toLowerCase() === RESOLVED_VIEW) {
|
|
10866
|
+
throw new DocmetaError(
|
|
10867
|
+
`SQL error: ${message}; the resolved table is read-only \u2014 it is \`docs\` and the evidence joined, so write to docs, or stamp the evidence with manni meta derive.`
|
|
10868
|
+
);
|
|
10869
|
+
}
|
|
8770
10870
|
throw new DocmetaError(
|
|
8771
|
-
`SQL error: ${message}; a collection is read-only \u2014 write through docs: UPDATE docs \u2026 WHERE _path IN (SELECT _path FROM "${viewName.replaceAll('"', '""')}").`
|
|
10871
|
+
`SQL error: ${message}; a collection or the derived table is read-only \u2014 write through docs: UPDATE docs \u2026 WHERE _path IN (SELECT _path FROM "${viewName.replaceAll('"', '""')}").`
|
|
8772
10872
|
);
|
|
8773
10873
|
}
|
|
8774
10874
|
throw new DocmetaError(`SQL error: ${message}`);
|
|
@@ -8781,7 +10881,7 @@ async function runSql(sql, entries, ctx) {
|
|
|
8781
10881
|
);
|
|
8782
10882
|
const { DatabaseSync } = await loadSqlite();
|
|
8783
10883
|
if (target) {
|
|
8784
|
-
await mkdir3(
|
|
10884
|
+
await mkdir3(dirname9(target.resolved), { recursive: true });
|
|
8785
10885
|
await prepareDbTarget(target.resolved, target.display);
|
|
8786
10886
|
}
|
|
8787
10887
|
let db;
|
|
@@ -8797,6 +10897,7 @@ async function runSql(sql, entries, ctx) {
|
|
|
8797
10897
|
files: entries.length,
|
|
8798
10898
|
columns: SYSTEM_COLUMNS.length + dataColumns.length
|
|
8799
10899
|
} : void 0;
|
|
10900
|
+
const derivedTable = { built: false };
|
|
8800
10901
|
try {
|
|
8801
10902
|
createDocsTable(db, entries, dataColumns);
|
|
8802
10903
|
let viewsBuilt = false;
|
|
@@ -8813,9 +10914,36 @@ async function runSql(sql, entries, ctx) {
|
|
|
8813
10914
|
viewsBuilt = true;
|
|
8814
10915
|
};
|
|
8815
10916
|
if (target) buildViews();
|
|
10917
|
+
const buildDerived = async () => {
|
|
10918
|
+
if (derivedTable.built) return;
|
|
10919
|
+
const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => ({
|
|
10920
|
+
label: e.label,
|
|
10921
|
+
absPath: resolve14(ctx.base, e.label),
|
|
10922
|
+
content: e.content,
|
|
10923
|
+
extracted: e.own
|
|
10924
|
+
}));
|
|
10925
|
+
const commands = commandsOf(ctx.config?.derive);
|
|
10926
|
+
const records = await deriveForTable(
|
|
10927
|
+
inputs,
|
|
10928
|
+
{
|
|
10929
|
+
cwd: ctx.cwd,
|
|
10930
|
+
base: ctx.base,
|
|
10931
|
+
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
10932
|
+
config: ctx.config,
|
|
10933
|
+
cache: ctx.cache,
|
|
10934
|
+
...ctx.now !== void 0 ? { now: ctx.now } : {}
|
|
10935
|
+
},
|
|
10936
|
+
"narrow derive.sources in manni.config.yaml",
|
|
10937
|
+
fieldsForSql(sql, derivableFields(commands))
|
|
10938
|
+
);
|
|
10939
|
+
createDerivedView(db, records, derivedColumns(commands));
|
|
10940
|
+
createResolvedView(db, dataColumns, commands);
|
|
10941
|
+
derivedTable.built = true;
|
|
10942
|
+
};
|
|
8816
10943
|
if (sql === "") {
|
|
8817
10944
|
return { columns: [], rows: [], ...dbInfo ? { db: dbInfo } : {} };
|
|
8818
10945
|
}
|
|
10946
|
+
if (mentionsDerived(sql) || mentionsResolved(sql)) await buildDerived();
|
|
8819
10947
|
const head = stripLeadingTrivia(sql);
|
|
8820
10948
|
if (/^(attach|vacuum)\b/i.test(head)) {
|
|
8821
10949
|
throw new DocmetaError(
|
|
@@ -8842,6 +10970,10 @@ async function runSql(sql, entries, ctx) {
|
|
|
8842
10970
|
if (missing !== void 0 && !viewsBuilt && namesConfiguredCollection(missing, ctx.declaredCollections)) {
|
|
8843
10971
|
throw new MissingCollectionView();
|
|
8844
10972
|
}
|
|
10973
|
+
const missingBare = missing?.replace(/^(main|temp)\./i, "").toLowerCase();
|
|
10974
|
+
if (!derivedTable.built && (missingBare === DERIVED_VIEW || missingBare === RESOLVED_VIEW)) {
|
|
10975
|
+
throw new MissingDerivedTable();
|
|
10976
|
+
}
|
|
8845
10977
|
refuseSqlError(err);
|
|
8846
10978
|
}
|
|
8847
10979
|
const before = snapshotRows(db);
|
|
@@ -8900,11 +11032,20 @@ async function runSql(sql, entries, ctx) {
|
|
|
8900
11032
|
try {
|
|
8901
11033
|
return await runOnce();
|
|
8902
11034
|
} catch (err) {
|
|
11035
|
+
if (err instanceof MissingDerivedTable) {
|
|
11036
|
+
await buildDerived();
|
|
11037
|
+
return await runOnce();
|
|
11038
|
+
}
|
|
8903
11039
|
if (!(err instanceof MissingCollectionView)) throw err;
|
|
8904
11040
|
buildViews();
|
|
8905
11041
|
return await runOnce();
|
|
8906
11042
|
}
|
|
8907
11043
|
} finally {
|
|
11044
|
+
if (target && derivedTable.built) {
|
|
11045
|
+
db.exec(
|
|
11046
|
+
`DROP VIEW IF EXISTS ${RESOLVED_VIEW}; DROP VIEW IF EXISTS ${DERIVED_VIEW}; DROP TABLE IF EXISTS ${DERIVED_ROWS}`
|
|
11047
|
+
);
|
|
11048
|
+
}
|
|
8908
11049
|
db.close();
|
|
8909
11050
|
}
|
|
8910
11051
|
}
|
|
@@ -9056,21 +11197,21 @@ function appendedColumnDef(before, after) {
|
|
|
9056
11197
|
const comma = /^\s*,\s*/.exec(appended);
|
|
9057
11198
|
return comma ? appended.slice(comma[0].length) : void 0;
|
|
9058
11199
|
}
|
|
9059
|
-
function skipWs(
|
|
11200
|
+
function skipWs(text2, from) {
|
|
9060
11201
|
let i = from;
|
|
9061
|
-
while (i <
|
|
11202
|
+
while (i < text2.length && /\s/.test(text2[i] ?? "")) i++;
|
|
9062
11203
|
return i;
|
|
9063
11204
|
}
|
|
9064
|
-
function indexOfCheck(
|
|
11205
|
+
function indexOfCheck(text2, from) {
|
|
9065
11206
|
let i = from;
|
|
9066
|
-
while (i <
|
|
9067
|
-
const ch =
|
|
9068
|
-
if (startsStringOrIdent(
|
|
9069
|
-
i = skipStringOrIdent(
|
|
9070
|
-
} else if (startsComment(
|
|
9071
|
-
i = skipComment(
|
|
11207
|
+
while (i < text2.length) {
|
|
11208
|
+
const ch = text2[i];
|
|
11209
|
+
if (startsStringOrIdent(text2, i)) {
|
|
11210
|
+
i = skipStringOrIdent(text2, i);
|
|
11211
|
+
} else if (startsComment(text2, i)) {
|
|
11212
|
+
i = skipComment(text2, i);
|
|
9072
11213
|
} else if (ch !== void 0 && /[A-Za-z_]/.test(ch)) {
|
|
9073
|
-
const m = /^[A-Za-z_][A-Za-z0-9_]*/.exec(
|
|
11214
|
+
const m = /^[A-Za-z_][A-Za-z0-9_]*/.exec(text2.slice(i));
|
|
9074
11215
|
const word = m?.[0] ?? "";
|
|
9075
11216
|
if (word.toUpperCase() === "CHECK") return i;
|
|
9076
11217
|
i += Math.max(word.length, 1);
|
|
@@ -9080,17 +11221,17 @@ function indexOfCheck(text, from) {
|
|
|
9080
11221
|
}
|
|
9081
11222
|
return -1;
|
|
9082
11223
|
}
|
|
9083
|
-
function matchingParenEnd(
|
|
11224
|
+
function matchingParenEnd(text2, open2) {
|
|
9084
11225
|
let depth = 0;
|
|
9085
11226
|
let i = open2;
|
|
9086
|
-
while (i <
|
|
9087
|
-
const ch =
|
|
9088
|
-
if (startsStringOrIdent(
|
|
9089
|
-
i = skipStringOrIdent(
|
|
11227
|
+
while (i < text2.length) {
|
|
11228
|
+
const ch = text2[i];
|
|
11229
|
+
if (startsStringOrIdent(text2, i)) {
|
|
11230
|
+
i = skipStringOrIdent(text2, i);
|
|
9090
11231
|
continue;
|
|
9091
11232
|
}
|
|
9092
|
-
if (startsComment(
|
|
9093
|
-
i = skipComment(
|
|
11233
|
+
if (startsComment(text2, i)) {
|
|
11234
|
+
i = skipComment(text2, i);
|
|
9094
11235
|
continue;
|
|
9095
11236
|
}
|
|
9096
11237
|
if (ch === "(") depth++;
|
|
@@ -9110,9 +11251,9 @@ function enumForAddedColumn(catalogBefore, catalogAfter, key, mapped) {
|
|
|
9110
11251
|
};
|
|
9111
11252
|
const def = appendedColumnDef(catalogBefore, catalogAfter);
|
|
9112
11253
|
if (def === void 0) {
|
|
9113
|
-
const count = (
|
|
11254
|
+
const count = (text2) => {
|
|
9114
11255
|
let n = 0;
|
|
9115
|
-
for (let i2 = indexOfCheck(
|
|
11256
|
+
for (let i2 = indexOfCheck(text2, 0); i2 !== -1; i2 = indexOfCheck(text2, i2 + 5)) {
|
|
9116
11257
|
n++;
|
|
9117
11258
|
}
|
|
9118
11259
|
return n;
|
|
@@ -9225,8 +11366,8 @@ var FORK_IGNORE_TEXT = {
|
|
|
9225
11366
|
refusal: (target) => `Refusing to fork into "${target}": git reports it as ignored. The forked schema must be committed \u2014 an ignored copy validates on this machine and is simply missing in CI, where the failure reads as a schema nobody changed. Drop the .gitignore rule covering this path, or evolve a tracked local schema instead.`,
|
|
9226
11367
|
unchecked: (where) => `could not check .gitignore for "${where}" (no repository here, or no git on PATH). The forked schema must be committed \u2014 make sure this path is tracked.`
|
|
9227
11368
|
};
|
|
9228
|
-
function matchEol(reference,
|
|
9229
|
-
return reference.includes("\r\n") ?
|
|
11369
|
+
function matchEol(reference, text2) {
|
|
11370
|
+
return reference.includes("\r\n") ? text2.replace(/\n/g, "\r\n") : text2;
|
|
9230
11371
|
}
|
|
9231
11372
|
function displayPath(ctx, abs) {
|
|
9232
11373
|
const rel = posixRelative(ctx.base, abs);
|
|
@@ -9240,14 +11381,14 @@ function assertSchemaWriteWithin(ctx, abs) {
|
|
|
9240
11381
|
);
|
|
9241
11382
|
}
|
|
9242
11383
|
function foldPath(p) {
|
|
9243
|
-
const abs =
|
|
11384
|
+
const abs = resolve14(p);
|
|
9244
11385
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
9245
11386
|
}
|
|
9246
11387
|
function filePinsByPath(ctx) {
|
|
9247
11388
|
const byPath = /* @__PURE__ */ new Map();
|
|
9248
11389
|
for (const [ref, pin] of collectSchemaPins(ctx.config)) {
|
|
9249
11390
|
if (classifyRef(ref).kind !== "file") continue;
|
|
9250
|
-
byPath.set(foldPath(
|
|
11391
|
+
byPath.set(foldPath(resolve14(ctx.cwd, ref)), pin);
|
|
9251
11392
|
}
|
|
9252
11393
|
return byPath;
|
|
9253
11394
|
}
|
|
@@ -9275,13 +11416,13 @@ async function loadSetMembers(refs, ctx) {
|
|
|
9275
11416
|
});
|
|
9276
11417
|
continue;
|
|
9277
11418
|
}
|
|
9278
|
-
const abs =
|
|
11419
|
+
const abs = resolve14(ctx.cwd, ref);
|
|
9279
11420
|
const canon = foldPath(abs);
|
|
9280
11421
|
if (seenFiles.has(canon)) continue;
|
|
9281
11422
|
seenFiles.add(canon);
|
|
9282
|
-
let
|
|
11423
|
+
let text2;
|
|
9283
11424
|
try {
|
|
9284
|
-
|
|
11425
|
+
text2 = await readFile10(abs, "utf8");
|
|
9285
11426
|
} catch {
|
|
9286
11427
|
throw new DocmetaError(
|
|
9287
11428
|
`Schema file not found: "${ref}" (looked at ${abs}).`
|
|
@@ -9289,7 +11430,7 @@ async function loadSetMembers(refs, ctx) {
|
|
|
9289
11430
|
}
|
|
9290
11431
|
const pin = pins.get(canon);
|
|
9291
11432
|
if (pin?.integrity !== void 0) {
|
|
9292
|
-
if (integrityOf(Buffer.from(
|
|
11433
|
+
if (integrityOf(Buffer.from(text2, "utf8")) !== pin.integrity) {
|
|
9293
11434
|
throw new DocmetaError(
|
|
9294
11435
|
`Schema "${ref}" does not match its recorded integrity; its contents have moved since it was vendored. Re-vendor it before evolving it.`
|
|
9295
11436
|
);
|
|
@@ -9297,7 +11438,7 @@ async function loadSetMembers(refs, ctx) {
|
|
|
9297
11438
|
}
|
|
9298
11439
|
let parsed2;
|
|
9299
11440
|
try {
|
|
9300
|
-
parsed2 = JSON.parse(stripBom(
|
|
11441
|
+
parsed2 = JSON.parse(stripBom(text2));
|
|
9301
11442
|
} catch {
|
|
9302
11443
|
throw new DocmetaError(
|
|
9303
11444
|
`Schema "${ref}" is not valid JSON, so DDL cannot read it. Fix the file first.`
|
|
@@ -9312,7 +11453,7 @@ async function loadSetMembers(refs, ctx) {
|
|
|
9312
11453
|
ref,
|
|
9313
11454
|
kind: "file",
|
|
9314
11455
|
abs,
|
|
9315
|
-
text,
|
|
11456
|
+
text: text2,
|
|
9316
11457
|
schema: parsed2
|
|
9317
11458
|
});
|
|
9318
11459
|
}
|
|
@@ -9480,16 +11621,16 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
9480
11621
|
const [, name2, ver] = target.builtinId.split(":");
|
|
9481
11622
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
9482
11623
|
const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
|
|
9483
|
-
schemaAbs =
|
|
11624
|
+
schemaAbs = resolve14(ctx.configDir ?? ctx.cwd, "schemas", forkName);
|
|
9484
11625
|
assertSchemaWriteWithin(ctx, schemaAbs);
|
|
9485
|
-
if (
|
|
11626
|
+
if (existsSync5(schemaAbs)) {
|
|
9486
11627
|
throw new DocmetaError(
|
|
9487
11628
|
`"${displayPath(ctx, schemaAbs)}" already exists; refusing to overwrite it with a fork of ${target.builtinId}.`
|
|
9488
11629
|
);
|
|
9489
11630
|
}
|
|
9490
11631
|
await assertNotIgnored(
|
|
9491
11632
|
schemaAbs,
|
|
9492
|
-
|
|
11633
|
+
dirname9(schemaAbs),
|
|
9493
11634
|
ctx.cwd,
|
|
9494
11635
|
ctx.onNotice,
|
|
9495
11636
|
FORK_IGNORE_TEXT
|
|
@@ -9636,8 +11777,8 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9636
11777
|
if (configPath === void 0 || configDir === void 0) {
|
|
9637
11778
|
return { changes: [], repointed: false };
|
|
9638
11779
|
}
|
|
9639
|
-
const
|
|
9640
|
-
const doc = parseDocument5(stripBom(
|
|
11780
|
+
const text2 = await readFile10(configPath, "utf8");
|
|
11781
|
+
const doc = parseDocument5(stripBom(text2));
|
|
9641
11782
|
const display = displayPath(ctx, configPath);
|
|
9642
11783
|
const rawRefOf = (item) => {
|
|
9643
11784
|
if (isScalar3(item) && typeof item.value === "string") return item.value;
|
|
@@ -9647,7 +11788,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9647
11788
|
}
|
|
9648
11789
|
return void 0;
|
|
9649
11790
|
};
|
|
9650
|
-
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute5(raw) ?
|
|
11791
|
+
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute5(raw) ? resolve14(configDir, raw) : raw;
|
|
9651
11792
|
const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
|
|
9652
11793
|
const seqResolvesToRunSet = (node) => {
|
|
9653
11794
|
if (!isSeq2(node)) return false;
|
|
@@ -9692,7 +11833,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9692
11833
|
if (!isMap4(item)) continue;
|
|
9693
11834
|
const raw = rawRefOf(item);
|
|
9694
11835
|
if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
|
|
9695
|
-
const abs = isAbsolute5(raw) ? raw :
|
|
11836
|
+
const abs = isAbsolute5(raw) ? raw : resolve14(configDir, raw);
|
|
9696
11837
|
if (foldPath(abs) !== wantPath) continue;
|
|
9697
11838
|
pinFrom = item.get("integrity");
|
|
9698
11839
|
item.set("integrity", edit.integrity.value);
|
|
@@ -9724,8 +11865,8 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9724
11865
|
return {
|
|
9725
11866
|
write: {
|
|
9726
11867
|
path: configPath,
|
|
9727
|
-
content: matchEol(
|
|
9728
|
-
expected:
|
|
11868
|
+
content: matchEol(text2, doc.toString({ lineWidth: 0 })),
|
|
11869
|
+
expected: text2
|
|
9729
11870
|
},
|
|
9730
11871
|
changes,
|
|
9731
11872
|
repointed
|
|
@@ -9908,12 +12049,12 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9908
12049
|
}
|
|
9909
12050
|
}
|
|
9910
12051
|
validateNewPath(file, ctx.base);
|
|
9911
|
-
if (!extractorForExtension(
|
|
12052
|
+
if (!extractorForExtension(extname6(file))?.apply) {
|
|
9912
12053
|
throw new DocmetaError(
|
|
9913
12054
|
`"${file}": no writable format for that extension.`
|
|
9914
12055
|
);
|
|
9915
12056
|
}
|
|
9916
|
-
if (
|
|
12057
|
+
if (existsSync5(resolve14(ctx.base, file))) {
|
|
9917
12058
|
throw new DocmetaError(
|
|
9918
12059
|
`"${file}" already exists; INSERT creates new files only.`
|
|
9919
12060
|
);
|
|
@@ -9927,12 +12068,12 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9927
12068
|
}
|
|
9928
12069
|
for (const { from, to } of diff.renamedFiles) {
|
|
9929
12070
|
validateNewPath(to, ctx.base);
|
|
9930
|
-
if (
|
|
12071
|
+
if (extname6(to).toLowerCase() !== extname6(from).toLowerCase()) {
|
|
9931
12072
|
throw new DocmetaError(
|
|
9932
12073
|
`"${from}" -> "${to}": a rename may not change the extension.`
|
|
9933
12074
|
);
|
|
9934
12075
|
}
|
|
9935
|
-
if (
|
|
12076
|
+
if (existsSync5(resolve14(ctx.base, to))) {
|
|
9936
12077
|
throw new DocmetaError(
|
|
9937
12078
|
`"${to}" already exists; the rename would overwrite it.`
|
|
9938
12079
|
);
|
|
@@ -9940,8 +12081,23 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9940
12081
|
changes.push({ file: from, renamed: to, written: false });
|
|
9941
12082
|
}
|
|
9942
12083
|
refuseExternalMetadataWrites(changes, entries, ctx);
|
|
12084
|
+
refuseManagedWrites(changes, entries, ctx);
|
|
9943
12085
|
return changes;
|
|
9944
12086
|
}
|
|
12087
|
+
function refuseManagedWrites(changes, entries, ctx) {
|
|
12088
|
+
if (ctx.managed.size === 0) return;
|
|
12089
|
+
const refuse = (key) => {
|
|
12090
|
+
throw new DocmetaError(
|
|
12091
|
+
`"${key}" is managed by derive; run manni meta derive instead.`
|
|
12092
|
+
);
|
|
12093
|
+
};
|
|
12094
|
+
const data = new Map(entries.map((e) => [e.label, e.extracted.data]));
|
|
12095
|
+
for (const c of changes) {
|
|
12096
|
+
if ("schema" in c || "config" in c || "renamed" in c) continue;
|
|
12097
|
+
const keys = "cleared" in c ? Object.keys(data.get(c.file) ?? {}) : "created" in c ? Object.keys(c.to) : "renamedFrom" in c ? [c.key, c.renamedFrom] : [c.key];
|
|
12098
|
+
for (const key of keys) if (ctx.managed.has(key)) refuse(key);
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
9945
12101
|
function validateNewPath(p, base) {
|
|
9946
12102
|
const refuse = (why) => {
|
|
9947
12103
|
throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
|
|
@@ -9950,9 +12106,9 @@ function validateNewPath(p, base) {
|
|
|
9950
12106
|
if (p.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
9951
12107
|
refuse("it traverses upward");
|
|
9952
12108
|
}
|
|
9953
|
-
const abs =
|
|
9954
|
-
if (abs ===
|
|
9955
|
-
if (!abs.startsWith(
|
|
12109
|
+
const abs = resolve14(base, p);
|
|
12110
|
+
if (abs === resolve14(base)) refuse("it points at the corpus root, not a file");
|
|
12111
|
+
if (!abs.startsWith(resolve14(base) + sep7)) refuse("it escapes the corpus");
|
|
9956
12112
|
}
|
|
9957
12113
|
function refuseExternalMetadataWrites(changes, entries, ctx) {
|
|
9958
12114
|
const index = ctx.externalMetadata;
|
|
@@ -9981,7 +12137,7 @@ function refuseExternalMetadataWrites(changes, entries, ctx) {
|
|
|
9981
12137
|
continue;
|
|
9982
12138
|
}
|
|
9983
12139
|
if ("renamed" in c) {
|
|
9984
|
-
const entry = index.byPath.get(
|
|
12140
|
+
const entry = index.byPath.get(resolve14(ctx.base, c.file));
|
|
9985
12141
|
if (entry !== void 0 && entry.size > 0) {
|
|
9986
12142
|
const manifest = [...entry.values()][0]?.file ?? "manifest";
|
|
9987
12143
|
throw new DocmetaError(
|
|
@@ -10034,15 +12190,15 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10034
12190
|
const pendingWrites = [];
|
|
10035
12191
|
const pendingRenames = [];
|
|
10036
12192
|
for (const [label, ops] of grouped) {
|
|
10037
|
-
const path =
|
|
12193
|
+
const path = resolve14(ctx.base, label);
|
|
10038
12194
|
if (ops.renamedTo !== void 0) {
|
|
10039
|
-
if (!
|
|
12195
|
+
if (!existsSync5(path)) {
|
|
10040
12196
|
throw new DocmetaError(
|
|
10041
12197
|
`"${label}" is gone from disk since it was read; re-run the query.`
|
|
10042
12198
|
);
|
|
10043
12199
|
}
|
|
10044
|
-
const toDisk =
|
|
10045
|
-
if (
|
|
12200
|
+
const toDisk = resolve14(ctx.base, ops.renamedTo);
|
|
12201
|
+
if (existsSync5(toDisk)) {
|
|
10046
12202
|
throw new DocmetaError(
|
|
10047
12203
|
`"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
|
|
10048
12204
|
);
|
|
@@ -10051,13 +12207,13 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10051
12207
|
continue;
|
|
10052
12208
|
}
|
|
10053
12209
|
if (ops.created !== void 0) {
|
|
10054
|
-
const extractor = extractorForExtension(
|
|
12210
|
+
const extractor = extractorForExtension(extname6(label));
|
|
10055
12211
|
if (!extractor?.apply) {
|
|
10056
12212
|
throw new DocmetaError(
|
|
10057
12213
|
`"${label}": no writable format for that extension.`
|
|
10058
12214
|
);
|
|
10059
12215
|
}
|
|
10060
|
-
if (
|
|
12216
|
+
if (existsSync5(path)) {
|
|
10061
12217
|
throw new DocmetaError(
|
|
10062
12218
|
`"${label}" appeared on disk since the plan; re-run the query.`
|
|
10063
12219
|
);
|
|
@@ -10076,7 +12232,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10076
12232
|
}
|
|
10077
12233
|
const entry = byLabel.get(label);
|
|
10078
12234
|
if (!entry) throw new DocmetaError(`No loaded entry for "${label}".`);
|
|
10079
|
-
const content = await
|
|
12235
|
+
const content = await readFile10(path, "utf8");
|
|
10080
12236
|
const members = ctx.memberships(label);
|
|
10081
12237
|
const current = mergeExternalMetadata(
|
|
10082
12238
|
label,
|
|
@@ -10144,7 +12300,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10144
12300
|
for (const w of schemaWrites) {
|
|
10145
12301
|
const display = displayPath(ctx, w.path);
|
|
10146
12302
|
if (w.expected === void 0) {
|
|
10147
|
-
if (
|
|
12303
|
+
if (existsSync5(w.path)) {
|
|
10148
12304
|
throw new DocmetaError(
|
|
10149
12305
|
`"${display}" appeared on disk since the plan; re-run the query.`
|
|
10150
12306
|
);
|
|
@@ -10153,7 +12309,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10153
12309
|
}
|
|
10154
12310
|
let now;
|
|
10155
12311
|
try {
|
|
10156
|
-
now = await
|
|
12312
|
+
now = await readFile10(w.path, "utf8");
|
|
10157
12313
|
} catch {
|
|
10158
12314
|
now = void 0;
|
|
10159
12315
|
}
|
|
@@ -10164,10 +12320,10 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
10164
12320
|
}
|
|
10165
12321
|
}
|
|
10166
12322
|
const dirs = /* @__PURE__ */ new Set();
|
|
10167
|
-
for (const w of schemaWrites) dirs.add(
|
|
10168
|
-
for (const r of pendingRenames) dirs.add(
|
|
12323
|
+
for (const w of schemaWrites) dirs.add(dirname9(w.path));
|
|
12324
|
+
for (const r of pendingRenames) dirs.add(dirname9(r.to));
|
|
10169
12325
|
for (const p of pendingWrites) {
|
|
10170
|
-
if (p.ensureDir) dirs.add(
|
|
12326
|
+
if (p.ensureDir) dirs.add(dirname9(p.path));
|
|
10171
12327
|
}
|
|
10172
12328
|
for (const d of dirs) await mkdir3(d, { recursive: true });
|
|
10173
12329
|
const writeSchema = async () => {
|
|
@@ -10282,8 +12438,8 @@ function skipExpression(sql, from) {
|
|
|
10282
12438
|
}
|
|
10283
12439
|
|
|
10284
12440
|
// src/meta/commands/fill.ts
|
|
10285
|
-
import { readFile as
|
|
10286
|
-
import { resolve as
|
|
12441
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
12442
|
+
import { resolve as resolve15, extname as extname7, join as join7 } from "node:path";
|
|
10287
12443
|
import {
|
|
10288
12444
|
completeValidatedJSON,
|
|
10289
12445
|
makeProvider,
|
|
@@ -10485,6 +12641,7 @@ async function runFill(opts) {
|
|
|
10485
12641
|
);
|
|
10486
12642
|
const dryRun = Boolean(opts.dryRun);
|
|
10487
12643
|
const only = opts.fields != null ? new Set(opts.fields) : void 0;
|
|
12644
|
+
const managed = new Set(config?.derive?.fields ?? []);
|
|
10488
12645
|
const requestedProvider = opts.provider ?? config?.fill?.provider ?? DEFAULT_PROVIDER;
|
|
10489
12646
|
const providerName = opts.local === true && requestedProvider === "auto" ? "llama-cpp" : requestedProvider;
|
|
10490
12647
|
const model = opts.model ?? config?.fill?.model;
|
|
@@ -10550,7 +12707,7 @@ async function runFill(opts) {
|
|
|
10550
12707
|
}
|
|
10551
12708
|
return provider;
|
|
10552
12709
|
};
|
|
10553
|
-
const cache = opts.cache === false ? void 0 : new JsonCache(
|
|
12710
|
+
const cache = opts.cache === false ? void 0 : new JsonCache(join7(cwd, CACHE_DIR), true, "manni");
|
|
10554
12711
|
const pricing = pricingFor(identity.model);
|
|
10555
12712
|
const validator = new Validator(schemaOptions);
|
|
10556
12713
|
let costUsd = 0;
|
|
@@ -10634,12 +12791,21 @@ async function runFill(opts) {
|
|
|
10634
12791
|
}
|
|
10635
12792
|
return errorResult(label, extractor.name, err.message);
|
|
10636
12793
|
}
|
|
10637
|
-
const
|
|
12794
|
+
const proposed = collectCandidates(
|
|
10638
12795
|
schemas,
|
|
10639
12796
|
extracted.data,
|
|
10640
12797
|
existingErrors,
|
|
10641
12798
|
only
|
|
10642
12799
|
);
|
|
12800
|
+
const managedSkips = proposed.filter((c) => managed.has(c.key)).map((c) => ({
|
|
12801
|
+
field: `/${c.key}`,
|
|
12802
|
+
required: c.required,
|
|
12803
|
+
confidence: 0,
|
|
12804
|
+
reasoning: "managed by derive",
|
|
12805
|
+
written: false,
|
|
12806
|
+
skipReason: "managed"
|
|
12807
|
+
}));
|
|
12808
|
+
const candidates = proposed.filter((c) => !managed.has(c.key));
|
|
10643
12809
|
const ownedCandidates = externalMetadata ? candidates.flatMap((c) => {
|
|
10644
12810
|
const owner = externalMetadata.owners.get(c.key)?.[0];
|
|
10645
12811
|
return owner === void 0 ? [] : [{ key: c.key, file: owner.file }];
|
|
@@ -10658,7 +12824,7 @@ async function runFill(opts) {
|
|
|
10658
12824
|
file: label,
|
|
10659
12825
|
format: extractor.name,
|
|
10660
12826
|
schemas: schemaSet,
|
|
10661
|
-
fields:
|
|
12827
|
+
fields: managedSkips,
|
|
10662
12828
|
changed: false,
|
|
10663
12829
|
...opts.includeContent ? { content } : {}
|
|
10664
12830
|
};
|
|
@@ -10727,9 +12893,9 @@ async function runFill(opts) {
|
|
|
10727
12893
|
break;
|
|
10728
12894
|
}
|
|
10729
12895
|
inFlight++;
|
|
10730
|
-
let
|
|
12896
|
+
let run2;
|
|
10731
12897
|
try {
|
|
10732
|
-
|
|
12898
|
+
run2 = await completeValidatedJSON({
|
|
10733
12899
|
provider: getProvider(),
|
|
10734
12900
|
system: FILL_SYSTEM_PROMPT,
|
|
10735
12901
|
user: buildUserPrompt({
|
|
@@ -10742,22 +12908,22 @@ async function runFill(opts) {
|
|
|
10742
12908
|
schema: envelope,
|
|
10743
12909
|
validate
|
|
10744
12910
|
});
|
|
10745
|
-
costUsd += costOfUsage(
|
|
12911
|
+
costUsd += costOfUsage(run2.usage, pricing);
|
|
10746
12912
|
turnsUsed++;
|
|
10747
12913
|
} finally {
|
|
10748
12914
|
inFlight--;
|
|
10749
12915
|
}
|
|
10750
|
-
if (
|
|
12916
|
+
if (run2.error != null && looksLikeOverflow(run2.error)) {
|
|
10751
12917
|
overflowed = true;
|
|
10752
|
-
failure =
|
|
12918
|
+
failure = run2.error;
|
|
10753
12919
|
break;
|
|
10754
12920
|
}
|
|
10755
|
-
if (
|
|
10756
|
-
failure =
|
|
12921
|
+
if (run2.error != null || run2.result == null) {
|
|
12922
|
+
failure = run2.error ?? "The model returned no proposal.";
|
|
10757
12923
|
break;
|
|
10758
12924
|
}
|
|
10759
|
-
usageTotal = addUsage(usageTotal,
|
|
10760
|
-
collected.push(
|
|
12925
|
+
usageTotal = addUsage(usageTotal, run2.usage);
|
|
12926
|
+
collected.push(run2.result);
|
|
10761
12927
|
}
|
|
10762
12928
|
if (overflowed && attempt < 2) {
|
|
10763
12929
|
usageTotal = void 0;
|
|
@@ -10790,7 +12956,7 @@ async function runFill(opts) {
|
|
|
10790
12956
|
proposals = mergeProposals(sets);
|
|
10791
12957
|
cache?.set(cacheKey, { proposals, ...usageTotal ? { usage: usageTotal } : {} });
|
|
10792
12958
|
}
|
|
10793
|
-
const fields = gate(candidates, proposals, threshold);
|
|
12959
|
+
const fields = [...managedSkips, ...gate(candidates, proposals, threshold)];
|
|
10794
12960
|
const accepted = fields.filter((f) => f.written);
|
|
10795
12961
|
if (accepted.length > 0) {
|
|
10796
12962
|
const merged2 = { ...extracted.data, ...patchOf(accepted) };
|
|
@@ -10839,7 +13005,7 @@ async function runFill(opts) {
|
|
|
10839
13005
|
}
|
|
10840
13006
|
const changed = next !== content;
|
|
10841
13007
|
if (changed && !dryRun && label !== "<stdin>") {
|
|
10842
|
-
await writeFileAtomic(
|
|
13008
|
+
await writeFileAtomic(resolve15(base, label), next);
|
|
10843
13009
|
}
|
|
10844
13010
|
return {
|
|
10845
13011
|
file: label,
|
|
@@ -10861,8 +13027,8 @@ async function runFill(opts) {
|
|
|
10861
13027
|
);
|
|
10862
13028
|
}
|
|
10863
13029
|
const fileResults = await mapConcurrent(files, concurrency, async (file) => {
|
|
10864
|
-
const content = await
|
|
10865
|
-
return processOne(file, content,
|
|
13030
|
+
const content = await readFile11(resolve15(base, file), "utf8");
|
|
13031
|
+
return processOne(file, content, extname7(file));
|
|
10866
13032
|
});
|
|
10867
13033
|
results.push(...fileResults);
|
|
10868
13034
|
return {
|
|
@@ -11045,8 +13211,8 @@ function plannedFor(schemas) {
|
|
|
11045
13211
|
}
|
|
11046
13212
|
function withRenamedRefs(node, renames) {
|
|
11047
13213
|
if (Array.isArray(node)) {
|
|
11048
|
-
const
|
|
11049
|
-
return
|
|
13214
|
+
const items2 = node;
|
|
13215
|
+
return items2.map((item) => withRenamedRefs(item, renames));
|
|
11050
13216
|
}
|
|
11051
13217
|
if (typeof node !== "object" || node === null) return node;
|
|
11052
13218
|
const out = {};
|
|
@@ -11160,16 +13326,16 @@ function summarize(results, costUsd, cached) {
|
|
|
11160
13326
|
cached
|
|
11161
13327
|
};
|
|
11162
13328
|
}
|
|
11163
|
-
async function mapConcurrent(
|
|
11164
|
-
const out = new Array(
|
|
13329
|
+
async function mapConcurrent(items2, limit, fn) {
|
|
13330
|
+
const out = new Array(items2.length);
|
|
11165
13331
|
let next = 0;
|
|
11166
13332
|
const workers = Array.from(
|
|
11167
|
-
{ length: Math.max(1, Math.min(limit,
|
|
13333
|
+
{ length: Math.max(1, Math.min(limit, items2.length)) },
|
|
11168
13334
|
async () => {
|
|
11169
13335
|
for (; ; ) {
|
|
11170
13336
|
const i = next++;
|
|
11171
|
-
const item =
|
|
11172
|
-
if (i >=
|
|
13337
|
+
const item = items2[i];
|
|
13338
|
+
if (i >= items2.length || item === void 0) return;
|
|
11173
13339
|
out[i] = await fn(item);
|
|
11174
13340
|
}
|
|
11175
13341
|
}
|
|
@@ -11178,6 +13344,252 @@ async function mapConcurrent(items, limit, fn) {
|
|
|
11178
13344
|
return out;
|
|
11179
13345
|
}
|
|
11180
13346
|
|
|
13347
|
+
// src/meta/commands/derive.ts
|
|
13348
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
13349
|
+
import { extname as extname8, resolve as resolve16 } from "node:path";
|
|
13350
|
+
var SOURCE_HINT = "narrow --sources or --fields";
|
|
13351
|
+
async function runDerive(opts) {
|
|
13352
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
13353
|
+
const { config, inputs, base, configDir, collections, declaredCollections, fromCollections } = await resolveRunConfig({
|
|
13354
|
+
cwd,
|
|
13355
|
+
configPath: opts.configPath,
|
|
13356
|
+
noConfig: opts.noConfig,
|
|
13357
|
+
inputs: opts.inputs,
|
|
13358
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
13359
|
+
onConfigLoaded: opts.onConfigLoaded
|
|
13360
|
+
});
|
|
13361
|
+
if (inputs.includes(STDIN_TOKEN)) {
|
|
13362
|
+
throw new DocmetaError("cannot derive <stdin>: no history behind it");
|
|
13363
|
+
}
|
|
13364
|
+
if (inputs.length === 0) {
|
|
13365
|
+
throw new DocmetaError(
|
|
13366
|
+
"No files to derive. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
13367
|
+
);
|
|
13368
|
+
}
|
|
13369
|
+
const commands = commandsOf(config?.derive);
|
|
13370
|
+
const fields = resolveFields(opts.fields, config?.derive?.fields, commands);
|
|
13371
|
+
for (const field of fields) {
|
|
13372
|
+
const owner = manifestOwning(field, declaredCollections);
|
|
13373
|
+
if (owner !== void 0) {
|
|
13374
|
+
throw new DocmetaError(
|
|
13375
|
+
`"${field}" is owned by the manifest ${owner.file} on collection ${owner.name}; a managed field has one authority, and a manifest key already has one.`
|
|
13376
|
+
);
|
|
13377
|
+
}
|
|
13378
|
+
}
|
|
13379
|
+
const sources = resolveSources(opts.sources, config?.derive?.sources);
|
|
13380
|
+
const check = Boolean(opts.check);
|
|
13381
|
+
const dryRun = Boolean(opts.dryRun) || check;
|
|
13382
|
+
const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
|
|
13383
|
+
if (opts.as && !forcedExtractor) {
|
|
13384
|
+
throw new DocmetaError(
|
|
13385
|
+
`Unknown format "${opts.as}". Known formats: ${listFormats().map((f) => f.name).join(", ")}.`
|
|
13386
|
+
);
|
|
13387
|
+
}
|
|
13388
|
+
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
13389
|
+
const exts = opts.exts ?? forcedExtractor?.extensions;
|
|
13390
|
+
const exclude = opts.exclude ?? [];
|
|
13391
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
13392
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
13393
|
+
inputs,
|
|
13394
|
+
exts,
|
|
13395
|
+
exclude,
|
|
13396
|
+
cwd: base,
|
|
13397
|
+
allowEmpty,
|
|
13398
|
+
...gitignoreOptions({
|
|
13399
|
+
flag: opts.respectGitignore,
|
|
13400
|
+
configured: config?.respectGitignore,
|
|
13401
|
+
onNotice: opts.onNotice
|
|
13402
|
+
})
|
|
13403
|
+
});
|
|
13404
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
13405
|
+
assertNonEmpty({
|
|
13406
|
+
files,
|
|
13407
|
+
inputs,
|
|
13408
|
+
usingStdin: false,
|
|
13409
|
+
allowEmpty,
|
|
13410
|
+
exclude,
|
|
13411
|
+
exts,
|
|
13412
|
+
gitignoreSkipped,
|
|
13413
|
+
action: "derived"
|
|
13414
|
+
});
|
|
13415
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
13416
|
+
const errors = /* @__PURE__ */ new Map();
|
|
13417
|
+
for (const label of files) {
|
|
13418
|
+
const absPath = resolve16(base, label);
|
|
13419
|
+
const extractor = forcedExtractor ?? extractorForExtension(extname8(label));
|
|
13420
|
+
if (!extractor) {
|
|
13421
|
+
errors.set(label, {
|
|
13422
|
+
format: "unknown",
|
|
13423
|
+
message: `Unsupported file type "${extname8(label)}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
13424
|
+
});
|
|
13425
|
+
continue;
|
|
13426
|
+
}
|
|
13427
|
+
const content = await readFile12(absPath, "utf8");
|
|
13428
|
+
const elements = resolveElements(label, config, membersFor(label));
|
|
13429
|
+
try {
|
|
13430
|
+
loaded.set(label, {
|
|
13431
|
+
label,
|
|
13432
|
+
absPath,
|
|
13433
|
+
content,
|
|
13434
|
+
format: extractor.name,
|
|
13435
|
+
elements,
|
|
13436
|
+
extracted: extractor.extract(content, label, { elements }),
|
|
13437
|
+
apply: extractor.apply
|
|
13438
|
+
});
|
|
13439
|
+
} catch (err) {
|
|
13440
|
+
errors.set(label, { format: extractor.name, message: errorMessage(err) });
|
|
13441
|
+
}
|
|
13442
|
+
}
|
|
13443
|
+
const derived = await deriveMetadata([...loaded.values()], {
|
|
13444
|
+
cwd,
|
|
13445
|
+
base,
|
|
13446
|
+
configDir,
|
|
13447
|
+
sources,
|
|
13448
|
+
fields,
|
|
13449
|
+
codeowners: config?.derive?.codeowners,
|
|
13450
|
+
commands,
|
|
13451
|
+
cache: opts.cache ?? true,
|
|
13452
|
+
now: opts.now ?? (() => /* @__PURE__ */ new Date()),
|
|
13453
|
+
reviews: opts.reviews
|
|
13454
|
+
});
|
|
13455
|
+
assertSourcesAvailable(derived.sources, SOURCE_HINT);
|
|
13456
|
+
for (const [name2, status] of Object.entries(derived.sources)) {
|
|
13457
|
+
if (status.available && status.reason !== void 0) {
|
|
13458
|
+
opts.onNotice?.(`${name2}: ${status.reason}`);
|
|
13459
|
+
}
|
|
13460
|
+
}
|
|
13461
|
+
const results = [];
|
|
13462
|
+
for (const label of files) {
|
|
13463
|
+
const failure = errors.get(label);
|
|
13464
|
+
if (failure) {
|
|
13465
|
+
results.push(errorResult2(label, failure.format, failure.message, check));
|
|
13466
|
+
continue;
|
|
13467
|
+
}
|
|
13468
|
+
const doc = loaded.get(label);
|
|
13469
|
+
if (!doc) continue;
|
|
13470
|
+
const record = derived.records.get(label);
|
|
13471
|
+
const compared = fields.map(
|
|
13472
|
+
(field) => compareDerived(field, doc.extracted.data[field], record?.fields[field])
|
|
13473
|
+
);
|
|
13474
|
+
const pending = compared.filter((f) => f.status === "stale" || f.status === "unset");
|
|
13475
|
+
let changed = false;
|
|
13476
|
+
if (pending.length > 0) {
|
|
13477
|
+
if (typeof doc.apply !== "function") {
|
|
13478
|
+
if (!dryRun) {
|
|
13479
|
+
results.push(
|
|
13480
|
+
errorResult2(
|
|
13481
|
+
label,
|
|
13482
|
+
doc.format,
|
|
13483
|
+
`The "${doc.format}" format is read-only; manni meta derive cannot write metadata back to it.`,
|
|
13484
|
+
check,
|
|
13485
|
+
compared
|
|
13486
|
+
)
|
|
13487
|
+
);
|
|
13488
|
+
continue;
|
|
13489
|
+
}
|
|
13490
|
+
changed = true;
|
|
13491
|
+
} else {
|
|
13492
|
+
const patch = {};
|
|
13493
|
+
for (const f of pending) patch[f.field] = f.derived;
|
|
13494
|
+
let next;
|
|
13495
|
+
try {
|
|
13496
|
+
next = doc.apply(doc.content, patch, { filePath: label, elements: doc.elements });
|
|
13497
|
+
} catch (err) {
|
|
13498
|
+
results.push(errorResult2(label, doc.format, errorMessage(err), check, compared));
|
|
13499
|
+
continue;
|
|
13500
|
+
}
|
|
13501
|
+
changed = next !== doc.content;
|
|
13502
|
+
if (changed && !dryRun) {
|
|
13503
|
+
await writeFileAtomic(doc.absPath, next);
|
|
13504
|
+
for (const f of pending) f.written = true;
|
|
13505
|
+
}
|
|
13506
|
+
}
|
|
13507
|
+
}
|
|
13508
|
+
results.push({
|
|
13509
|
+
file: label,
|
|
13510
|
+
format: doc.format,
|
|
13511
|
+
fields: compared,
|
|
13512
|
+
changed,
|
|
13513
|
+
...check ? { findings: staleFindings(compared, doc.extracted.lineFor) } : {}
|
|
13514
|
+
});
|
|
13515
|
+
}
|
|
13516
|
+
return {
|
|
13517
|
+
results,
|
|
13518
|
+
summary: summarize2(results),
|
|
13519
|
+
dryRun,
|
|
13520
|
+
check,
|
|
13521
|
+
sources: derived.sources,
|
|
13522
|
+
frame: { cwd, base: configDir ?? cwd, runBase: base }
|
|
13523
|
+
};
|
|
13524
|
+
}
|
|
13525
|
+
function resolveFields(flag, configured, commands) {
|
|
13526
|
+
const names = flag ?? configured;
|
|
13527
|
+
if (names === void 0 || names.length === 0) {
|
|
13528
|
+
throw new DocmetaError(
|
|
13529
|
+
"nothing to derive: set derive.fields in manni.config.yaml or pass --fields"
|
|
13530
|
+
);
|
|
13531
|
+
}
|
|
13532
|
+
const out = [];
|
|
13533
|
+
for (const name2 of names) {
|
|
13534
|
+
if (!isBuiltinField(name2) && !(commands !== void 0 && Object.hasOwn(commands, name2))) {
|
|
13535
|
+
throw new DocmetaError(
|
|
13536
|
+
`"${name2}" is not derivable; derivable fields are ${DERIVABLE_FIELDS.join(", ")}, or any key with an entry in derive.commands`
|
|
13537
|
+
);
|
|
13538
|
+
}
|
|
13539
|
+
if (!out.includes(name2)) out.push(name2);
|
|
13540
|
+
}
|
|
13541
|
+
return out;
|
|
13542
|
+
}
|
|
13543
|
+
function resolveSources(flag, configured) {
|
|
13544
|
+
const names = flag ?? configured ?? DERIVE_SOURCES;
|
|
13545
|
+
const out = [];
|
|
13546
|
+
for (const name2 of names) {
|
|
13547
|
+
if (!isDeriveSource(name2)) {
|
|
13548
|
+
throw new DocmetaError(
|
|
13549
|
+
`"${name2}" is not a source; sources are ${DERIVE_SOURCES.join(", ")}`
|
|
13550
|
+
);
|
|
13551
|
+
}
|
|
13552
|
+
if (!out.includes(name2)) out.push(name2);
|
|
13553
|
+
}
|
|
13554
|
+
return out;
|
|
13555
|
+
}
|
|
13556
|
+
function errorResult2(file, format, message, check, fields = []) {
|
|
13557
|
+
return {
|
|
13558
|
+
file,
|
|
13559
|
+
format,
|
|
13560
|
+
fields,
|
|
13561
|
+
changed: false,
|
|
13562
|
+
error: message,
|
|
13563
|
+
...check ? {
|
|
13564
|
+
findings: [
|
|
13565
|
+
{ schema: "(parse)", instancePath: "", message, keyword: "parse" }
|
|
13566
|
+
]
|
|
13567
|
+
} : {}
|
|
13568
|
+
};
|
|
13569
|
+
}
|
|
13570
|
+
function summarize2(results) {
|
|
13571
|
+
const summary = {
|
|
13572
|
+
files: results.length,
|
|
13573
|
+
changed: 0,
|
|
13574
|
+
written: 0,
|
|
13575
|
+
stale: 0,
|
|
13576
|
+
unset: 0,
|
|
13577
|
+
unknown: 0,
|
|
13578
|
+
errors: 0
|
|
13579
|
+
};
|
|
13580
|
+
for (const r of results) {
|
|
13581
|
+
if (r.changed) summary.changed++;
|
|
13582
|
+
if (r.error !== void 0) summary.errors++;
|
|
13583
|
+
for (const f of r.fields) {
|
|
13584
|
+
if (f.written) summary.written++;
|
|
13585
|
+
if (f.status === "stale") summary.stale++;
|
|
13586
|
+
else if (f.status === "unset") summary.unset++;
|
|
13587
|
+
else if (f.status === "unknown") summary.unknown++;
|
|
13588
|
+
}
|
|
13589
|
+
}
|
|
13590
|
+
return summary;
|
|
13591
|
+
}
|
|
13592
|
+
|
|
11181
13593
|
// src/meta/reporters/sarif.ts
|
|
11182
13594
|
import { isAbsolute as isAbsolute6 } from "node:path";
|
|
11183
13595
|
|
|
@@ -11187,11 +13599,13 @@ var PARSE_ERROR_RULE = "manni/parse-error";
|
|
|
11187
13599
|
var SCHEMA_ERROR_RULE = "manni/schema-error";
|
|
11188
13600
|
var EXTERNAL_OWNED_RULE = "external:owned/external";
|
|
11189
13601
|
var EXTERNAL_DUPLICATE_RULE = "external:duplicate/external";
|
|
13602
|
+
var DERIVED_STALE_RULE = "derived:stale/derived";
|
|
11190
13603
|
var RESERVED_RULES = {
|
|
11191
13604
|
[PARSE_ERROR_RULE]: "The document's metadata block could not be parsed.",
|
|
11192
13605
|
[SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document.",
|
|
11193
13606
|
[EXTERNAL_OWNED_RULE]: "The document carries a key a manifest owns; the manifest is the only place that key may be set.",
|
|
11194
|
-
[EXTERNAL_DUPLICATE_RULE]: "Two documents carry the same value of a manifest's join field, so one manifest entry matched both."
|
|
13607
|
+
[EXTERNAL_DUPLICATE_RULE]: "Two documents carry the same value of a manifest's join field, so one manifest entry matched both.",
|
|
13608
|
+
[DERIVED_STALE_RULE]: "A managed field whose value differs from what git, CODEOWNERS or the GitHub or GitLab review record says; run `manni meta derive` to refresh it."
|
|
11195
13609
|
};
|
|
11196
13610
|
function ruleIdFor(e, frame) {
|
|
11197
13611
|
if (e.schema === SYNTHETIC_SCHEMA) {
|
|
@@ -11518,14 +13932,14 @@ var FILL_FORMAT_LIST = formatList(FILL_FORMATS);
|
|
|
11518
13932
|
function isFillFormat(value) {
|
|
11519
13933
|
return FILL_FORMATS.includes(value);
|
|
11520
13934
|
}
|
|
11521
|
-
function renderFillJson(
|
|
11522
|
-
return JSON.stringify(
|
|
13935
|
+
function renderFillJson(run2) {
|
|
13936
|
+
return JSON.stringify(run2, null, 2);
|
|
11523
13937
|
}
|
|
11524
13938
|
var score = (n) => n.toFixed(2);
|
|
11525
|
-
function renderFillPretty(
|
|
13939
|
+
function renderFillPretty(run2, opts = {}) {
|
|
11526
13940
|
const c = palette(opts.color ?? false);
|
|
11527
13941
|
const lines = [];
|
|
11528
|
-
for (const result of
|
|
13942
|
+
for (const result of run2.results) {
|
|
11529
13943
|
if (result.error != null) {
|
|
11530
13944
|
lines.push(`${c.red("\u2717")} ${result.file}`);
|
|
11531
13945
|
lines.push(` ${result.error}`);
|
|
@@ -11549,7 +13963,7 @@ function renderFillPretty(run, opts = {}) {
|
|
|
11549
13963
|
if (low.length > 0) {
|
|
11550
13964
|
lines.push(
|
|
11551
13965
|
c.dim(
|
|
11552
|
-
` below ${
|
|
13966
|
+
` below ${run2.threshold}: ${low.map((f) => `${f.field} ${score(f.confidence)}`).join(", ")}`
|
|
11553
13967
|
)
|
|
11554
13968
|
);
|
|
11555
13969
|
}
|
|
@@ -11557,14 +13971,14 @@ function renderFillPretty(run, opts = {}) {
|
|
|
11557
13971
|
lines.push(c.dim(` ${f.skipReason ?? "skipped"}: ${f.field}`));
|
|
11558
13972
|
}
|
|
11559
13973
|
}
|
|
11560
|
-
const { summary } =
|
|
11561
|
-
const verb =
|
|
13974
|
+
const { summary } = run2;
|
|
13975
|
+
const verb = run2.dryRun ? "would be written" : "written";
|
|
11562
13976
|
const parts = [
|
|
11563
13977
|
// Which provider ran decides what the proposals and the cost mean, and
|
|
11564
13978
|
// under `auto` it is not something the user chose — so say it rather than
|
|
11565
13979
|
// making them pass -f json to find out.
|
|
11566
|
-
`${
|
|
11567
|
-
`Threshold ${
|
|
13980
|
+
`${run2.provider}/${run2.model}`,
|
|
13981
|
+
`Threshold ${run2.threshold}`,
|
|
11568
13982
|
`${summary.files} file${summary.files === 1 ? "" : "s"}`,
|
|
11569
13983
|
`${summary.written} field${summary.written === 1 ? "" : "s"} ${verb}`,
|
|
11570
13984
|
`${summary.skipped} skipped`
|
|
@@ -11582,7 +13996,7 @@ function renderFillPretty(run, opts = {}) {
|
|
|
11582
13996
|
)
|
|
11583
13997
|
);
|
|
11584
13998
|
}
|
|
11585
|
-
if (
|
|
13999
|
+
if (run2.turnsSpent) {
|
|
11586
14000
|
lines.push(
|
|
11587
14001
|
c.yellow("--max-turns reached; some files were not processed.")
|
|
11588
14002
|
);
|
|
@@ -11593,9 +14007,9 @@ function formatValue(value) {
|
|
|
11593
14007
|
if (typeof value === "string") return value;
|
|
11594
14008
|
return toJsonText(value) ?? String(value);
|
|
11595
14009
|
}
|
|
11596
|
-
function renderFillGithub(
|
|
14010
|
+
function renderFillGithub(run2) {
|
|
11597
14011
|
const lines = [];
|
|
11598
|
-
for (const result of
|
|
14012
|
+
for (const result of run2.results) {
|
|
11599
14013
|
const file = escapeWorkflowCommandProperty(result.file);
|
|
11600
14014
|
if (result.error != null) {
|
|
11601
14015
|
lines.push(
|
|
@@ -11605,7 +14019,7 @@ function renderFillGithub(run) {
|
|
|
11605
14019
|
}
|
|
11606
14020
|
for (const f of result.fields) {
|
|
11607
14021
|
if (!f.required || f.written) continue;
|
|
11608
|
-
const why = f.skipReason === "low-confidence" ? `confidence ${score(f.confidence)} is below the ${
|
|
14022
|
+
const why = f.skipReason === "low-confidence" ? `confidence ${score(f.confidence)} is below the ${run2.threshold} threshold` : f.skipReason ?? "skipped";
|
|
11609
14023
|
const msg = escapeWorkflowCommandMessage(
|
|
11610
14024
|
`[fill] ${f.field} is required and was not filled (${why})`
|
|
11611
14025
|
);
|
|
@@ -11614,14 +14028,14 @@ function renderFillGithub(run) {
|
|
|
11614
14028
|
}
|
|
11615
14029
|
return lines.join("\n");
|
|
11616
14030
|
}
|
|
11617
|
-
function renderFill(format,
|
|
14031
|
+
function renderFill(format, run2, opts = {}) {
|
|
11618
14032
|
switch (format) {
|
|
11619
14033
|
case "json":
|
|
11620
|
-
return renderFillJson(
|
|
14034
|
+
return renderFillJson(run2);
|
|
11621
14035
|
case "github":
|
|
11622
|
-
return renderFillGithub(
|
|
14036
|
+
return renderFillGithub(run2);
|
|
11623
14037
|
case "pretty":
|
|
11624
|
-
return renderFillPretty(
|
|
14038
|
+
return renderFillPretty(run2, opts);
|
|
11625
14039
|
default: {
|
|
11626
14040
|
const unreachable = format;
|
|
11627
14041
|
throw new DocmetaError(
|
|
@@ -11631,12 +14045,137 @@ function renderFill(format, run, opts = {}) {
|
|
|
11631
14045
|
}
|
|
11632
14046
|
}
|
|
11633
14047
|
|
|
14048
|
+
// src/meta/reporters/derive.ts
|
|
14049
|
+
var DERIVE_FORMATS = ["pretty", "json", "github", "sarif", "junit"];
|
|
14050
|
+
var DERIVE_FORMAT_LIST = formatList(DERIVE_FORMATS);
|
|
14051
|
+
function isDeriveFormat(value) {
|
|
14052
|
+
return DERIVE_FORMATS.includes(value);
|
|
14053
|
+
}
|
|
14054
|
+
var JUNIT_CLASSNAME = "manni.derive";
|
|
14055
|
+
function renderDerive(run2, format, opts = {}) {
|
|
14056
|
+
if (run2.check) {
|
|
14057
|
+
const { results, summary } = asValidation(run2);
|
|
14058
|
+
return render(format, results, summary, {
|
|
14059
|
+
classname: JUNIT_CLASSNAME,
|
|
14060
|
+
...opts
|
|
14061
|
+
});
|
|
14062
|
+
}
|
|
14063
|
+
switch (format) {
|
|
14064
|
+
case "json":
|
|
14065
|
+
return JSON.stringify(run2, null, 2);
|
|
14066
|
+
case "pretty":
|
|
14067
|
+
return renderDerivePretty(run2, opts);
|
|
14068
|
+
case "github":
|
|
14069
|
+
case "sarif":
|
|
14070
|
+
case "junit":
|
|
14071
|
+
throw new DocmetaError(
|
|
14072
|
+
`${format} is a findings format, which only --check produces`
|
|
14073
|
+
);
|
|
14074
|
+
default: {
|
|
14075
|
+
const unreachable = format;
|
|
14076
|
+
throw new DocmetaError(
|
|
14077
|
+
`Unknown report format ${JSON.stringify(unreachable)}. Use ${DERIVE_FORMAT_LIST}.`
|
|
14078
|
+
);
|
|
14079
|
+
}
|
|
14080
|
+
}
|
|
14081
|
+
}
|
|
14082
|
+
function asValidation(run2) {
|
|
14083
|
+
const results = run2.results.map((r) => {
|
|
14084
|
+
const errors = r.findings ?? [];
|
|
14085
|
+
return { file: r.file, format: r.format, ok: errors.length === 0, schemas: [], errors };
|
|
14086
|
+
});
|
|
14087
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
14088
|
+
return {
|
|
14089
|
+
results,
|
|
14090
|
+
summary: {
|
|
14091
|
+
files: results.length,
|
|
14092
|
+
passed: results.length - failed,
|
|
14093
|
+
failed,
|
|
14094
|
+
errors: results.reduce((n, r) => n + r.errors.length, 0)
|
|
14095
|
+
}
|
|
14096
|
+
};
|
|
14097
|
+
}
|
|
14098
|
+
var plural2 = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
14099
|
+
function renderDerivePretty(run2, opts = {}) {
|
|
14100
|
+
const c = palette(opts.color ?? false);
|
|
14101
|
+
const lines = [];
|
|
14102
|
+
for (const result of run2.results) {
|
|
14103
|
+
if (result.error !== void 0) {
|
|
14104
|
+
lines.push(`${c.red("\u2717")} ${result.file}`);
|
|
14105
|
+
lines.push(` ${result.error}`);
|
|
14106
|
+
continue;
|
|
14107
|
+
}
|
|
14108
|
+
const pending = result.fields.filter((f) => f.status === "stale" || f.status === "unset");
|
|
14109
|
+
if (pending.length === 0) {
|
|
14110
|
+
if (opts.quiet) continue;
|
|
14111
|
+
lines.push(`${result.file} ${c.dim("current")}${unknownNote(result, c.dim)}`);
|
|
14112
|
+
continue;
|
|
14113
|
+
}
|
|
14114
|
+
lines.push(result.file);
|
|
14115
|
+
const nameWidth = Math.max(...pending.map((f) => f.field.length));
|
|
14116
|
+
const changeWidth = Math.max(...pending.map((f) => change(f).length));
|
|
14117
|
+
for (const f of pending) {
|
|
14118
|
+
const name2 = c.cyan(f.field.padEnd(nameWidth));
|
|
14119
|
+
const from = f.status === "unset" ? c.dim("(unset)") : c.dim(fmt2(f.asserted));
|
|
14120
|
+
const to = c.green(fmt2(f.derived));
|
|
14121
|
+
const pad = " ".repeat(changeWidth - change(f).length);
|
|
14122
|
+
const trail = c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`);
|
|
14123
|
+
lines.push(` ${name2} ${from} \u2192 ${to}${pad} ${trail}`);
|
|
14124
|
+
}
|
|
14125
|
+
const note = unknownNote(result, c.dim);
|
|
14126
|
+
if (note !== "") lines.push(` ${note}`);
|
|
14127
|
+
}
|
|
14128
|
+
const s = run2.summary;
|
|
14129
|
+
const parts = [plural2(s.files, "file")];
|
|
14130
|
+
if (run2.dryRun) {
|
|
14131
|
+
parts.push(`${s.changed} would change`, plural2(s.stale + s.unset, "field"));
|
|
14132
|
+
} else {
|
|
14133
|
+
parts.push(`${s.changed} changed`, `${plural2(s.written, "field")} written`);
|
|
14134
|
+
}
|
|
14135
|
+
if (s.errors > 0) parts.push(plural2(s.errors, "error"));
|
|
14136
|
+
let footer = parts.join(", ");
|
|
14137
|
+
if (run2.dryRun) footer += " \u2014 dry run, nothing written";
|
|
14138
|
+
if (lines.length > 0) lines.push("");
|
|
14139
|
+
lines.push(s.errors > 0 ? c.yellow(footer) : footer);
|
|
14140
|
+
return lines.join("\n");
|
|
14141
|
+
}
|
|
14142
|
+
function change(f) {
|
|
14143
|
+
const from = f.status === "unset" ? "(unset)" : fmt2(f.asserted);
|
|
14144
|
+
return `${from} \u2192 ${fmt2(f.derived)}`;
|
|
14145
|
+
}
|
|
14146
|
+
function unknownNote(result, dim) {
|
|
14147
|
+
const unknown = result.fields.filter((f) => f.status === "unknown").map((f) => f.field);
|
|
14148
|
+
if (unknown.length === 0) return "";
|
|
14149
|
+
return dim(` (${unknown.join(", ")}: no source could answer)`);
|
|
14150
|
+
}
|
|
14151
|
+
function fmt2(value) {
|
|
14152
|
+
if (typeof value === "string") return value;
|
|
14153
|
+
return toJsonText(value) ?? String(value);
|
|
14154
|
+
}
|
|
14155
|
+
|
|
11634
14156
|
// src/meta/reporters/get.ts
|
|
11635
14157
|
function stringifyValue(value) {
|
|
11636
14158
|
if (value === void 0) return "(unset)";
|
|
11637
14159
|
if (typeof value === "string") return value;
|
|
11638
14160
|
return JSON.stringify(value);
|
|
11639
14161
|
}
|
|
14162
|
+
function evidenceOf(d) {
|
|
14163
|
+
const repeated = ` (${stringifyValue(d.value)})`;
|
|
14164
|
+
return d.evidence.endsWith(repeated) ? d.evidence.slice(0, -repeated.length) : d.evidence;
|
|
14165
|
+
}
|
|
14166
|
+
function annotation(r, field) {
|
|
14167
|
+
const origin = r.origin?.[field];
|
|
14168
|
+
if (origin === void 0) return "";
|
|
14169
|
+
const d = r.derived?.[field] ?? null;
|
|
14170
|
+
if (origin === "derived") {
|
|
14171
|
+
return d == null ? "" : ` (derived, ${d.source}: ${evidenceOf(d)})`;
|
|
14172
|
+
}
|
|
14173
|
+
if (d == null) return " (asserted)";
|
|
14174
|
+
if (compareDerived(field, r.resolved?.[field], d).status === "current") {
|
|
14175
|
+
return " (asserted)";
|
|
14176
|
+
}
|
|
14177
|
+
return ` (asserted; ${d.source} says ${stringifyValue(d.value)}, ${evidenceOf(d)})`;
|
|
14178
|
+
}
|
|
11640
14179
|
function renderGet(results, fields, opts = {}) {
|
|
11641
14180
|
const c = palette(opts.color ?? false);
|
|
11642
14181
|
const lines = [];
|
|
@@ -11645,9 +14184,14 @@ function renderGet(results, fields, opts = {}) {
|
|
|
11645
14184
|
lines.push(`${c.dim(`${r.file}:`)} ${c.red(`(parse) ${r.error}`)}`);
|
|
11646
14185
|
continue;
|
|
11647
14186
|
}
|
|
11648
|
-
|
|
14187
|
+
const effective = r.resolved ?? r.values;
|
|
14188
|
+
if (opts.quiet && fields.every((f) => effective[f] === void 0)) continue;
|
|
11649
14189
|
for (const f of fields) {
|
|
11650
|
-
|
|
14190
|
+
const note = annotation(r, f);
|
|
14191
|
+
const suffix = note === "" ? "" : c.dim(note);
|
|
14192
|
+
lines.push(
|
|
14193
|
+
`${c.dim(`${r.file}:`)} ${f}=${stringifyValue(effective[f])}${suffix}`
|
|
14194
|
+
);
|
|
11651
14195
|
}
|
|
11652
14196
|
}
|
|
11653
14197
|
return lines.join("\n");
|
|
@@ -11658,21 +14202,21 @@ function cell(value) {
|
|
|
11658
14202
|
if (value === null) return "(null)";
|
|
11659
14203
|
return typeof value === "bigint" ? String(value) : stringifyValue(value);
|
|
11660
14204
|
}
|
|
11661
|
-
function renderQuery(
|
|
14205
|
+
function renderQuery(run2, opts = {}) {
|
|
11662
14206
|
const c = palette(opts.color ?? false);
|
|
11663
|
-
if (
|
|
14207
|
+
if (run2.changes) return renderChanges(run2.changes, c, opts);
|
|
11664
14208
|
const lines = [];
|
|
11665
|
-
if (
|
|
11666
|
-
const widths =
|
|
11667
|
-
(col) => Math.max(col.length, ...
|
|
14209
|
+
if (run2.rows.length > 0) {
|
|
14210
|
+
const widths = run2.columns.map(
|
|
14211
|
+
(col) => Math.max(col.length, ...run2.rows.map((r) => cell(r[col]).length))
|
|
11668
14212
|
);
|
|
11669
|
-
const pad = (
|
|
11670
|
-
lines.push(
|
|
11671
|
-
for (const row of
|
|
11672
|
-
lines.push(
|
|
14213
|
+
const pad = (text2, i) => i === run2.columns.length - 1 ? text2 : text2.padEnd(widths[i] ?? 0);
|
|
14214
|
+
lines.push(run2.columns.map((col, i) => c.bold(pad(col, i))).join(" "));
|
|
14215
|
+
for (const row of run2.rows) {
|
|
14216
|
+
lines.push(run2.columns.map((col, i) => pad(cell(row[col]), i)).join(" "));
|
|
11673
14217
|
}
|
|
11674
14218
|
}
|
|
11675
|
-
const n =
|
|
14219
|
+
const n = run2.rows.length;
|
|
11676
14220
|
const count = `${n} row${n === 1 ? "" : "s"}`;
|
|
11677
14221
|
lines.push(
|
|
11678
14222
|
opts.check ? n > 0 ? c.red(`\u2717 ${count} \u2014 check failed`) : c.green(`\u2713 ${count}`) : c.dim(count)
|
|
@@ -11680,15 +14224,15 @@ function renderQuery(run, opts = {}) {
|
|
|
11680
14224
|
return lines.join("\n");
|
|
11681
14225
|
}
|
|
11682
14226
|
var CSV_NEEDS_QUOTING = /[",\n\r]/;
|
|
11683
|
-
function renderQueryCsv(
|
|
14227
|
+
function renderQueryCsv(run2) {
|
|
11684
14228
|
const field = (value) => {
|
|
11685
14229
|
if (value === null || value === void 0) return "";
|
|
11686
|
-
const
|
|
11687
|
-
return CSV_NEEDS_QUOTING.test(
|
|
14230
|
+
const text2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : stringifyValue(value);
|
|
14231
|
+
return CSV_NEEDS_QUOTING.test(text2) ? `"${text2.replaceAll('"', '""')}"` : text2;
|
|
11688
14232
|
};
|
|
11689
14233
|
return [
|
|
11690
|
-
|
|
11691
|
-
...
|
|
14234
|
+
run2.columns.map(field).join(","),
|
|
14235
|
+
...run2.rows.map((row) => run2.columns.map((c) => field(row[c])).join(","))
|
|
11692
14236
|
].join("\n");
|
|
11693
14237
|
}
|
|
11694
14238
|
function renderChanges(changes, c, opts) {
|
|
@@ -11809,12 +14353,46 @@ export {
|
|
|
11809
14353
|
orphanEntries,
|
|
11810
14354
|
orphanJoins,
|
|
11811
14355
|
orphanError,
|
|
14356
|
+
DERIVABLE_FIELDS,
|
|
14357
|
+
isBuiltinField,
|
|
14358
|
+
DERIVE_SOURCES,
|
|
14359
|
+
isDeriveSource,
|
|
14360
|
+
DERIVED_STALE_SCHEMA,
|
|
14361
|
+
DERIVED_KEYWORD,
|
|
14362
|
+
compareDerived,
|
|
14363
|
+
staleFindings,
|
|
11812
14364
|
parseConfig,
|
|
11813
14365
|
loadConfig,
|
|
11814
14366
|
resolveRunConfig,
|
|
11815
14367
|
isParamName,
|
|
11816
14368
|
memberOf,
|
|
11817
14369
|
Validator,
|
|
14370
|
+
REVIEW_CACHE_DIR,
|
|
14371
|
+
REVIEW_CACHE_VERSION,
|
|
14372
|
+
ReviewCache,
|
|
14373
|
+
cachedClient,
|
|
14374
|
+
BinMissing,
|
|
14375
|
+
run,
|
|
14376
|
+
PATH_PLACEHOLDER,
|
|
14377
|
+
isPerFile,
|
|
14378
|
+
argvFor,
|
|
14379
|
+
valueOf,
|
|
14380
|
+
deriveFromCommands,
|
|
14381
|
+
parseOriginUrl,
|
|
14382
|
+
identityFromOrigin,
|
|
14383
|
+
GitHubClient,
|
|
14384
|
+
GitLabClient,
|
|
14385
|
+
createReviewClient,
|
|
14386
|
+
deriveFromReviews,
|
|
14387
|
+
FIELD_SOURCES,
|
|
14388
|
+
sourcesFor,
|
|
14389
|
+
consultedSources,
|
|
14390
|
+
assertSourcesAvailable,
|
|
14391
|
+
deriveMetadata,
|
|
14392
|
+
RESOLVED_VIEW,
|
|
14393
|
+
resolvedColumns,
|
|
14394
|
+
mentionsResolved,
|
|
14395
|
+
createResolvedView,
|
|
11818
14396
|
rowsToFindings,
|
|
11819
14397
|
runValidate,
|
|
11820
14398
|
runGet,
|
|
@@ -11825,6 +14403,7 @@ export {
|
|
|
11825
14403
|
runInferSchema,
|
|
11826
14404
|
runQuery,
|
|
11827
14405
|
runFill,
|
|
14406
|
+
runDerive,
|
|
11828
14407
|
renderSarif,
|
|
11829
14408
|
renderJunit,
|
|
11830
14409
|
REPORT_FORMATS,
|
|
@@ -11845,9 +14424,13 @@ export {
|
|
|
11845
14424
|
isFillFormat,
|
|
11846
14425
|
renderFillGithub,
|
|
11847
14426
|
renderFill,
|
|
14427
|
+
DERIVE_FORMATS,
|
|
14428
|
+
DERIVE_FORMAT_LIST,
|
|
14429
|
+
isDeriveFormat,
|
|
14430
|
+
renderDerive,
|
|
11848
14431
|
stringifyValue,
|
|
11849
14432
|
renderGet,
|
|
11850
14433
|
renderQuery,
|
|
11851
14434
|
renderQueryCsv
|
|
11852
14435
|
};
|
|
11853
|
-
//# sourceMappingURL=chunk-
|
|
14436
|
+
//# sourceMappingURL=chunk-HYPDUKRU.js.map
|