@hawkeyexl/manni 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-PDNSN5UJ.js → chunk-2DC2GSRH.js} +2077 -312
- package/dist/chunk-2DC2GSRH.js.map +1 -0
- package/dist/{chunk-MV2T7XAW.js → chunk-HNPRXGD7.js} +16 -58
- package/dist/chunk-HNPRXGD7.js.map +1 -0
- package/dist/{chunk-QLVNSAHR.js → chunk-UTXTVCBV.js} +156 -547
- package/dist/chunk-UTXTVCBV.js.map +1 -0
- package/dist/cli.js +10 -18
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +4 -3
- package/dist/docmeta.js.map +1 -1
- package/dist/index.d.ts +257 -68
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-MV2T7XAW.js.map +0 -1
- package/dist/chunk-PDNSN5UJ.js.map +0 -1
- package/dist/chunk-QLVNSAHR.js.map +0 -1
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@hawkeyexl/manni",
|
|
11
|
-
version: "2.0
|
|
11
|
+
version: "2.1.0",
|
|
12
12
|
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.",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -124,6 +124,85 @@ var package_default = {
|
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
// src/shared/run.ts
|
|
128
|
+
import { realpathSync } from "node:fs";
|
|
129
|
+
import { fileURLToPath } from "node:url";
|
|
130
|
+
import { CommanderError } from "commander";
|
|
131
|
+
|
|
132
|
+
// src/shared/errors.ts
|
|
133
|
+
var ToolError = class extends Error {
|
|
134
|
+
/** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
|
|
135
|
+
exitCode = 2;
|
|
136
|
+
constructor(message) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = "ToolError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function errorMessage(err) {
|
|
142
|
+
if (err instanceof Error) return err.message;
|
|
143
|
+
if (typeof err === "string") return err;
|
|
144
|
+
if (typeof err === "number" || typeof err === "bigint") return String(err);
|
|
145
|
+
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
146
|
+
return err.message;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const json = JSON.stringify(err);
|
|
150
|
+
if (json !== void 0) return json;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
return String(err);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/shared/program-name.ts
|
|
157
|
+
var name = "manni";
|
|
158
|
+
function programName() {
|
|
159
|
+
return name;
|
|
160
|
+
}
|
|
161
|
+
function setProgramName(value) {
|
|
162
|
+
name = value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/shared/run.ts
|
|
166
|
+
function fail(err) {
|
|
167
|
+
const msg = err instanceof ToolError ? err.message : `Unexpected error: ${errorMessage(err)}`;
|
|
168
|
+
process.stderr.write(`${programName()}: ${msg}
|
|
169
|
+
`);
|
|
170
|
+
process.exit(2);
|
|
171
|
+
}
|
|
172
|
+
var STDIN_LINES_MARKER = "\0";
|
|
173
|
+
var STDIN_WITH_LINES = /^-(:[1-9][0-9]*(?:-[1-9][0-9]*)?)$/;
|
|
174
|
+
function normalizeStdinArgv(argv) {
|
|
175
|
+
return argv.map((arg) => {
|
|
176
|
+
const lines = STDIN_WITH_LINES.exec(arg)?.[1];
|
|
177
|
+
return lines === void 0 ? arg : `${STDIN_LINES_MARKER}${lines}`;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async function runProgram(program, argv = process.argv) {
|
|
181
|
+
setProgramName(program.name());
|
|
182
|
+
try {
|
|
183
|
+
await program.parseAsync(normalizeStdinArgv(argv));
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (err instanceof CommanderError) {
|
|
186
|
+
process.exitCode = err.exitCode === 0 ? 0 : 2;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function isMainModule(moduleUrl) {
|
|
193
|
+
const entry = process.argv[1];
|
|
194
|
+
if (!entry) return false;
|
|
195
|
+
try {
|
|
196
|
+
return realpathSync(entry) === fileURLToPath(moduleUrl);
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function runIfMain(moduleUrl, build) {
|
|
202
|
+
if (!isMainModule(moduleUrl)) return;
|
|
203
|
+
runProgram(build()).catch(fail);
|
|
204
|
+
}
|
|
205
|
+
|
|
127
206
|
// src/shared/collections.ts
|
|
128
207
|
import picomatch2 from "picomatch";
|
|
129
208
|
|
|
@@ -392,13 +471,53 @@ function isMember(collection, relPath) {
|
|
|
392
471
|
return !collection.exclude.some((glob) => matchesFileGlob(relPath, glob));
|
|
393
472
|
}
|
|
394
473
|
function normalizeEntry(entry) {
|
|
395
|
-
const
|
|
396
|
-
return
|
|
474
|
+
const posix4 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
475
|
+
return posix4.endsWith("/") ? posix4.slice(0, -1) : posix4;
|
|
397
476
|
}
|
|
398
477
|
function isGlob(entry) {
|
|
399
478
|
return picomatch2.scan(entry).isGlob;
|
|
400
479
|
}
|
|
401
480
|
|
|
481
|
+
// src/meta/core/integrity.ts
|
|
482
|
+
import { createHash } from "node:crypto";
|
|
483
|
+
var INTEGRITY_ALGORITHM = "sha256";
|
|
484
|
+
var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
|
|
485
|
+
var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
|
|
486
|
+
function integrityOf(bytes) {
|
|
487
|
+
return `${INTEGRITY_ALGORITHM}-${createHash(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
|
|
488
|
+
}
|
|
489
|
+
function isIntegrity(value) {
|
|
490
|
+
return INTEGRITY_RE.test(value);
|
|
491
|
+
}
|
|
492
|
+
function toLf(bytes) {
|
|
493
|
+
const out = new Uint8Array(bytes.length);
|
|
494
|
+
let n = 0;
|
|
495
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
496
|
+
const byte = bytes[i];
|
|
497
|
+
if (byte === void 0) continue;
|
|
498
|
+
if (byte === 13 && bytes[i + 1] === 10) continue;
|
|
499
|
+
out[n++] = byte;
|
|
500
|
+
}
|
|
501
|
+
return out.subarray(0, n);
|
|
502
|
+
}
|
|
503
|
+
function toCrlf(bytes) {
|
|
504
|
+
const lf = toLf(bytes);
|
|
505
|
+
const out = new Uint8Array(lf.length * 2);
|
|
506
|
+
let n = 0;
|
|
507
|
+
for (let i = 0; i < lf.length; i++) {
|
|
508
|
+
const byte = lf[i];
|
|
509
|
+
if (byte === void 0) continue;
|
|
510
|
+
if (byte === 10) out[n++] = 13;
|
|
511
|
+
out[n++] = byte;
|
|
512
|
+
}
|
|
513
|
+
return out.subarray(0, n);
|
|
514
|
+
}
|
|
515
|
+
function diagnoseIntegrity(bytes, expected) {
|
|
516
|
+
if (integrityOf(toLf(bytes)) === expected) return "line-endings";
|
|
517
|
+
if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
|
|
518
|
+
return "content";
|
|
519
|
+
}
|
|
520
|
+
|
|
402
521
|
// src/meta/extractors/frontmatter.ts
|
|
403
522
|
import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
|
|
404
523
|
import { parse as parseToml } from "smol-toml";
|
|
@@ -453,22 +572,22 @@ function locateFrontmatter(content) {
|
|
|
453
572
|
while (cursor <= body.length) {
|
|
454
573
|
const nl = body.indexOf("\n", cursor);
|
|
455
574
|
const hasNl = nl !== -1;
|
|
456
|
-
const
|
|
457
|
-
const hadCr =
|
|
458
|
-
const line = body.slice(cursor, hadCr ?
|
|
575
|
+
const lineEnd2 = hasNl ? nl : body.length;
|
|
576
|
+
const hadCr = lineEnd2 > cursor && body.charCodeAt(lineEnd2 - 1) === 13;
|
|
577
|
+
const line = body.slice(cursor, hadCr ? lineEnd2 - 1 : lineEnd2);
|
|
459
578
|
if (fence.isClose(line)) {
|
|
460
579
|
return {
|
|
461
580
|
flavor: fence.flavor,
|
|
462
581
|
openStart: bom,
|
|
463
582
|
innerStart: innerStart + bom,
|
|
464
583
|
innerEnd: cursor + bom,
|
|
465
|
-
closeEnd: (hasNl ?
|
|
584
|
+
closeEnd: (hasNl ? lineEnd2 + 1 : lineEnd2) + bom,
|
|
466
585
|
eol,
|
|
467
586
|
firstContentLine: 2
|
|
468
587
|
};
|
|
469
588
|
}
|
|
470
589
|
if (!hasNl) break;
|
|
471
|
-
cursor =
|
|
590
|
+
cursor = lineEnd2 + 1;
|
|
472
591
|
}
|
|
473
592
|
return null;
|
|
474
593
|
}
|
|
@@ -607,30 +726,6 @@ function extractFrontmatter(content, format) {
|
|
|
607
726
|
}
|
|
608
727
|
}
|
|
609
728
|
|
|
610
|
-
// src/shared/errors.ts
|
|
611
|
-
var ToolError = class extends Error {
|
|
612
|
-
/** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
|
|
613
|
-
exitCode = 2;
|
|
614
|
-
constructor(message) {
|
|
615
|
-
super(message);
|
|
616
|
-
this.name = "ToolError";
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
function errorMessage(err) {
|
|
620
|
-
if (err instanceof Error) return err.message;
|
|
621
|
-
if (typeof err === "string") return err;
|
|
622
|
-
if (typeof err === "number" || typeof err === "bigint") return String(err);
|
|
623
|
-
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
624
|
-
return err.message;
|
|
625
|
-
}
|
|
626
|
-
try {
|
|
627
|
-
const json = JSON.stringify(err);
|
|
628
|
-
if (json !== void 0) return json;
|
|
629
|
-
} catch {
|
|
630
|
-
}
|
|
631
|
-
return String(err);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
729
|
// src/meta/types.ts
|
|
635
730
|
function isErrorSeverity(e) {
|
|
636
731
|
return e.severity === void 0 || e.severity === "error";
|
|
@@ -1204,8 +1299,8 @@ function ditaShape(content, root, filePath) {
|
|
|
1204
1299
|
const lower = filePath?.toLowerCase() ?? "";
|
|
1205
1300
|
const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
|
|
1206
1301
|
if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
|
|
1207
|
-
const
|
|
1208
|
-
return
|
|
1302
|
+
const isMap7 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
|
|
1303
|
+
return isMap7 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
|
|
1209
1304
|
kind: "topic",
|
|
1210
1305
|
container: "metadata",
|
|
1211
1306
|
preamble: /* @__PURE__ */ new Set([
|
|
@@ -2633,24 +2728,13 @@ function listFormats() {
|
|
|
2633
2728
|
// src/meta/core/write-file.ts
|
|
2634
2729
|
import { writeFile, rename, rm, stat, chmod } from "node:fs/promises";
|
|
2635
2730
|
import { dirname, join, basename } from "node:path";
|
|
2636
|
-
|
|
2637
|
-
// src/shared/program-name.ts
|
|
2638
|
-
var name = "manni";
|
|
2639
|
-
function programName() {
|
|
2640
|
-
return name;
|
|
2641
|
-
}
|
|
2642
|
-
function setProgramName(value) {
|
|
2643
|
-
name = value;
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
// src/meta/core/write-file.ts
|
|
2647
2731
|
var LOCKED = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
2648
2732
|
var RENAME_ATTEMPTS = 3;
|
|
2649
2733
|
function errorCode(err) {
|
|
2650
2734
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
2651
2735
|
}
|
|
2652
|
-
var wait = (ms) => new Promise((
|
|
2653
|
-
setTimeout(
|
|
2736
|
+
var wait = (ms) => new Promise((resolve19) => {
|
|
2737
|
+
setTimeout(resolve19, ms);
|
|
2654
2738
|
});
|
|
2655
2739
|
async function writeFileAtomic(path, contents) {
|
|
2656
2740
|
const tmp = join(
|
|
@@ -2691,7 +2775,7 @@ async function writeFileAtomic(path, contents) {
|
|
|
2691
2775
|
}
|
|
2692
2776
|
|
|
2693
2777
|
// src/meta/core/schema-cache.ts
|
|
2694
|
-
import { createHash } from "node:crypto";
|
|
2778
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2695
2779
|
import { mkdir, readFile, stat as stat2 } from "node:fs/promises";
|
|
2696
2780
|
import { join as join2 } from "node:path";
|
|
2697
2781
|
var SCHEMA_CACHE_DIR = ".manni/meta/schema-cache";
|
|
@@ -2721,7 +2805,7 @@ var SchemaCache = class {
|
|
|
2721
2805
|
* directory.
|
|
2722
2806
|
*/
|
|
2723
2807
|
entryPath(url) {
|
|
2724
|
-
const key =
|
|
2808
|
+
const key = createHash2("sha256").update(url).digest("hex");
|
|
2725
2809
|
return join2(this.dir, `${key}.json`);
|
|
2726
2810
|
}
|
|
2727
2811
|
/**
|
|
@@ -2798,46 +2882,6 @@ var SchemaCache = class {
|
|
|
2798
2882
|
}
|
|
2799
2883
|
};
|
|
2800
2884
|
|
|
2801
|
-
// src/meta/core/integrity.ts
|
|
2802
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
2803
|
-
var INTEGRITY_ALGORITHM = "sha256";
|
|
2804
|
-
var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
|
|
2805
|
-
var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
|
|
2806
|
-
function integrityOf(bytes) {
|
|
2807
|
-
return `${INTEGRITY_ALGORITHM}-${createHash2(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
|
|
2808
|
-
}
|
|
2809
|
-
function isIntegrity(value) {
|
|
2810
|
-
return INTEGRITY_RE.test(value);
|
|
2811
|
-
}
|
|
2812
|
-
function toLf(bytes) {
|
|
2813
|
-
const out = new Uint8Array(bytes.length);
|
|
2814
|
-
let n = 0;
|
|
2815
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
2816
|
-
const byte = bytes[i];
|
|
2817
|
-
if (byte === void 0) continue;
|
|
2818
|
-
if (byte === 13 && bytes[i + 1] === 10) continue;
|
|
2819
|
-
out[n++] = byte;
|
|
2820
|
-
}
|
|
2821
|
-
return out.subarray(0, n);
|
|
2822
|
-
}
|
|
2823
|
-
function toCrlf(bytes) {
|
|
2824
|
-
const lf = toLf(bytes);
|
|
2825
|
-
const out = new Uint8Array(lf.length * 2);
|
|
2826
|
-
let n = 0;
|
|
2827
|
-
for (let i = 0; i < lf.length; i++) {
|
|
2828
|
-
const byte = lf[i];
|
|
2829
|
-
if (byte === void 0) continue;
|
|
2830
|
-
if (byte === 10) out[n++] = 13;
|
|
2831
|
-
out[n++] = byte;
|
|
2832
|
-
}
|
|
2833
|
-
return out.subarray(0, n);
|
|
2834
|
-
}
|
|
2835
|
-
function diagnoseIntegrity(bytes, expected) {
|
|
2836
|
-
if (integrityOf(toLf(bytes)) === expected) return "line-endings";
|
|
2837
|
-
if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
|
|
2838
|
-
return "content";
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
2885
|
// src/meta/core/schema-registry.ts
|
|
2842
2886
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
2843
2887
|
import { resolve as resolvePath } from "node:path";
|
|
@@ -5243,8 +5287,8 @@ var SCHEMA_KEYS = [
|
|
|
5243
5287
|
"multipleOf"
|
|
5244
5288
|
];
|
|
5245
5289
|
var RETRY_DELAY_MS = 500;
|
|
5246
|
-
var wait2 = (ms) => new Promise((
|
|
5247
|
-
setTimeout(
|
|
5290
|
+
var wait2 = (ms) => new Promise((resolve19) => {
|
|
5291
|
+
setTimeout(resolve19, ms);
|
|
5248
5292
|
});
|
|
5249
5293
|
var RetryableFetchError = class extends DocmetaError {
|
|
5250
5294
|
};
|
|
@@ -5659,8 +5703,10 @@ var DERIVABLE_FIELDS = [
|
|
|
5659
5703
|
"authors",
|
|
5660
5704
|
"owner",
|
|
5661
5705
|
"reviewed-by",
|
|
5662
|
-
"last-reviewed"
|
|
5706
|
+
"last-reviewed",
|
|
5707
|
+
"provenance"
|
|
5663
5708
|
];
|
|
5709
|
+
var PROVENANCE_FIELD = "provenance";
|
|
5664
5710
|
function isBuiltinField(x) {
|
|
5665
5711
|
return DERIVABLE_FIELDS.includes(x);
|
|
5666
5712
|
}
|
|
@@ -6145,7 +6191,7 @@ function asElementPaths(value, where, source) {
|
|
|
6145
6191
|
}
|
|
6146
6192
|
var OVERRIDE_KEYS = ["collection", "files", "schemas", "elements"];
|
|
6147
6193
|
var CHECK_KEYS = ["name", "query"];
|
|
6148
|
-
var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands"];
|
|
6194
|
+
var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands", "machines"];
|
|
6149
6195
|
var DERIVE_COMMAND_KEYS = ["run", "timeout"];
|
|
6150
6196
|
var BUILTIN_CLAIMED_BY = {
|
|
6151
6197
|
created: "git",
|
|
@@ -6153,7 +6199,8 @@ var BUILTIN_CLAIMED_BY = {
|
|
|
6153
6199
|
authors: "git",
|
|
6154
6200
|
owner: "codeowners",
|
|
6155
6201
|
"reviewed-by": "GitHub or GitLab",
|
|
6156
|
-
"last-reviewed": "GitHub or GitLab"
|
|
6202
|
+
"last-reviewed": "GitHub or GitLab",
|
|
6203
|
+
provenance: "git"
|
|
6157
6204
|
};
|
|
6158
6205
|
var DERIVED_TABLE_RESERVED = /* @__PURE__ */ new Set(["_path", "_sources"]);
|
|
6159
6206
|
var DERIVABLE_LIST = `Derivable fields: ${DERIVABLE_FIELDS.join(", ")}, or any key with an entry in derive.commands.`;
|
|
@@ -6398,6 +6445,7 @@ function assertManagedFieldsUnowned(config, collections, source, section) {
|
|
|
6398
6445
|
if (owner !== void 0) refuse(`derive.commands.${key}`, owner);
|
|
6399
6446
|
}
|
|
6400
6447
|
for (const [i, field] of (config.derive?.fields ?? []).entries()) {
|
|
6448
|
+
if (field === PROVENANCE_FIELD) continue;
|
|
6401
6449
|
const owner = manifestOwning(field, collections);
|
|
6402
6450
|
if (owner !== void 0) refuse(`derive.fields[${i}] "${field}"`, owner);
|
|
6403
6451
|
}
|
|
@@ -6410,9 +6458,9 @@ function parseDerive(raw, source) {
|
|
|
6410
6458
|
}
|
|
6411
6459
|
const e = raw;
|
|
6412
6460
|
rejectUnknownKeys2(e, DERIVE_KEYS, '"derive"', source);
|
|
6413
|
-
if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0) {
|
|
6461
|
+
if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0 && e.machines === void 0) {
|
|
6414
6462
|
throw new DocmetaError(
|
|
6415
|
-
`${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\` or \`
|
|
6463
|
+
`${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\`, \`codeowners:\` or \`machines:\` to shape the reads.`
|
|
6416
6464
|
);
|
|
6417
6465
|
}
|
|
6418
6466
|
const commands = e.commands === void 0 ? void 0 : parseDeriveCommands(e.commands, source);
|
|
@@ -6463,6 +6511,24 @@ function parseDerive(raw, source) {
|
|
|
6463
6511
|
derive.codeowners = e.codeowners;
|
|
6464
6512
|
}
|
|
6465
6513
|
if (commands !== void 0) derive.commands = commands;
|
|
6514
|
+
if (e.machines !== void 0) {
|
|
6515
|
+
if (!Array.isArray(e.machines) || e.machines.length === 0 || e.machines.some((m) => typeof m !== "string")) {
|
|
6516
|
+
throw new DocmetaError(
|
|
6517
|
+
`${source}: derive.machines must be a non-empty list of globs, matched against a trailer's name and its email. Default: ["*[bot]"].`
|
|
6518
|
+
);
|
|
6519
|
+
}
|
|
6520
|
+
const machines = [];
|
|
6521
|
+
e.machines.forEach((m, i) => {
|
|
6522
|
+
if (m.trim() === "") {
|
|
6523
|
+
throw new DocmetaError(`${source}: derive.machines[${i}] is blank; a glob must name something.`);
|
|
6524
|
+
}
|
|
6525
|
+
if (machines.includes(m)) {
|
|
6526
|
+
throw new DocmetaError(`${source}: derive.machines lists "${m}" twice.`);
|
|
6527
|
+
}
|
|
6528
|
+
machines.push(m);
|
|
6529
|
+
});
|
|
6530
|
+
derive.machines = machines;
|
|
6531
|
+
}
|
|
6466
6532
|
return derive;
|
|
6467
6533
|
}
|
|
6468
6534
|
function parseDeriveCommands(raw, source) {
|
|
@@ -7743,8 +7809,8 @@ var RENAME_ATTEMPTS2 = 3;
|
|
|
7743
7809
|
function errorCode2(err) {
|
|
7744
7810
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
7745
7811
|
}
|
|
7746
|
-
var wait3 = (ms) => new Promise((
|
|
7747
|
-
setTimeout(
|
|
7812
|
+
var wait3 = (ms) => new Promise((resolve19) => {
|
|
7813
|
+
setTimeout(resolve19, ms);
|
|
7748
7814
|
});
|
|
7749
7815
|
async function writeTextAtomic(path, text2) {
|
|
7750
7816
|
const tmp = join5(
|
|
@@ -8970,8 +9036,8 @@ var ReviewCache = class {
|
|
|
8970
9036
|
}
|
|
8971
9037
|
if (!isRecord2(raw)) return void 0;
|
|
8972
9038
|
if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
|
|
8973
|
-
const
|
|
8974
|
-
return
|
|
9039
|
+
const change = readChange(raw.change);
|
|
9040
|
+
return change ?? void 0;
|
|
8975
9041
|
}
|
|
8976
9042
|
set(key, value) {
|
|
8977
9043
|
if (!this.enabled) return;
|
|
@@ -9000,9 +9066,9 @@ function cachedClient(client, cache, identity) {
|
|
|
9000
9066
|
const key = `${identity.host}/${identity.project}/${sha}`;
|
|
9001
9067
|
const hit = cache.get(key);
|
|
9002
9068
|
if (hit !== void 0) return hit;
|
|
9003
|
-
const
|
|
9004
|
-
if (
|
|
9005
|
-
return
|
|
9069
|
+
const change = await client.mergedChangeFor(sha);
|
|
9070
|
+
if (change !== null) cache.set(key, change);
|
|
9071
|
+
return change;
|
|
9006
9072
|
}
|
|
9007
9073
|
};
|
|
9008
9074
|
}
|
|
@@ -9037,7 +9103,7 @@ function run(bin, args, opts) {
|
|
|
9037
9103
|
done = true;
|
|
9038
9104
|
settle(r);
|
|
9039
9105
|
};
|
|
9040
|
-
const
|
|
9106
|
+
const fail2 = (err) => {
|
|
9041
9107
|
if (done) return;
|
|
9042
9108
|
done = true;
|
|
9043
9109
|
reject(err);
|
|
@@ -9049,7 +9115,7 @@ function run(bin, args, opts) {
|
|
|
9049
9115
|
windowsHide: true
|
|
9050
9116
|
});
|
|
9051
9117
|
} catch (err) {
|
|
9052
|
-
|
|
9118
|
+
fail2(err instanceof Error ? err : new Error(String(err)));
|
|
9053
9119
|
return;
|
|
9054
9120
|
}
|
|
9055
9121
|
let stdout = "";
|
|
@@ -9085,7 +9151,7 @@ function run(bin, args, opts) {
|
|
|
9085
9151
|
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
|
|
9086
9152
|
child.on("error", (err) => {
|
|
9087
9153
|
clearTimeout(timer);
|
|
9088
|
-
|
|
9154
|
+
fail2(err.code === "ENOENT" ? new BinMissing(err.message) : err);
|
|
9089
9155
|
});
|
|
9090
9156
|
child.on("close", (code) => {
|
|
9091
9157
|
clearTimeout(timer);
|
|
@@ -9496,17 +9562,17 @@ async function deriveFromReviews(inputs, client) {
|
|
|
9496
9562
|
records.set(input.label, NOTHING);
|
|
9497
9563
|
continue;
|
|
9498
9564
|
}
|
|
9499
|
-
const
|
|
9500
|
-
records.set(input.label, factsFrom(
|
|
9565
|
+
const change = await changeFor(input.sha);
|
|
9566
|
+
records.set(input.label, factsFrom(change, kind));
|
|
9501
9567
|
}
|
|
9502
9568
|
return { status, records };
|
|
9503
9569
|
}
|
|
9504
|
-
function factsFrom(
|
|
9505
|
-
if (
|
|
9506
|
-
const ordered = byTime(
|
|
9570
|
+
function factsFrom(change, kind) {
|
|
9571
|
+
if (change === null || change.approvals.length === 0) return NOTHING;
|
|
9572
|
+
const ordered = byTime(change.approvals);
|
|
9507
9573
|
const latest = ordered.at(-1);
|
|
9508
9574
|
if (latest === void 0) return NOTHING;
|
|
9509
|
-
const evidence = kind === "gitlab" ? `gitlab MR !${
|
|
9575
|
+
const evidence = kind === "gitlab" ? `gitlab MR !${change.id}` : `github PR #${change.id}`;
|
|
9510
9576
|
const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
|
|
9511
9577
|
return {
|
|
9512
9578
|
"reviewed-by": {
|
|
@@ -9744,87 +9810,631 @@ function toPosix2(p) {
|
|
|
9744
9810
|
// src/meta/core/derive/git.ts
|
|
9745
9811
|
import { constants as bufferConstants } from "node:buffer";
|
|
9746
9812
|
import { spawn as spawn3 } from "node:child_process";
|
|
9747
|
-
import { dirname as dirname8, extname as extname2, relative as relative10, sep as sep5 } from "node:path";
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
);
|
|
9754
|
-
|
|
9755
|
-
};
|
|
9756
|
-
var BlobsUnreadable = class extends Error {
|
|
9757
|
-
constructor(detail) {
|
|
9758
|
-
super(detail);
|
|
9759
|
-
this.detail = detail;
|
|
9813
|
+
import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute5, posix as posix2, relative as relative10, sep as sep5 } from "node:path";
|
|
9814
|
+
import { parse as parseYaml4 } from "yaml";
|
|
9815
|
+
|
|
9816
|
+
// src/shared/pin.ts
|
|
9817
|
+
var LineRangeError = class extends ToolError {
|
|
9818
|
+
constructor(message) {
|
|
9819
|
+
super(message);
|
|
9820
|
+
this.name = "LineRangeError";
|
|
9760
9821
|
}
|
|
9761
|
-
detail;
|
|
9762
9822
|
};
|
|
9763
|
-
function
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9823
|
+
function normalizeText(text2) {
|
|
9824
|
+
const body = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
9825
|
+
return body.replace(/\r\n/g, "\n");
|
|
9826
|
+
}
|
|
9827
|
+
function splitLines(text2) {
|
|
9828
|
+
const parts = normalizeText(text2).split("\n");
|
|
9829
|
+
if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
|
|
9830
|
+
return parts;
|
|
9831
|
+
}
|
|
9832
|
+
function sliceLines(lines, range, label) {
|
|
9833
|
+
const start = range?.start;
|
|
9834
|
+
if (start === void 0) return lines.join("\n");
|
|
9835
|
+
const end = range?.end ?? start;
|
|
9836
|
+
if (end < start) {
|
|
9837
|
+
throw new LineRangeError(`${label ?? "the source"} range ${String(start)}-${String(end)} ends before it starts.`);
|
|
9838
|
+
}
|
|
9839
|
+
if (start > lines.length || end > lines.length) {
|
|
9840
|
+
const first = start > lines.length ? start : end;
|
|
9841
|
+
throw new LineRangeError(`${label ?? "the source"} has ${lines.length} lines; line ${first} is out of range.`);
|
|
9842
|
+
}
|
|
9843
|
+
return lines.slice(start - 1, end).join("\n");
|
|
9844
|
+
}
|
|
9845
|
+
function hashRange(text2, range, key) {
|
|
9846
|
+
return hashLines(sliceLines(splitLines(text2), range), key);
|
|
9847
|
+
}
|
|
9848
|
+
function hashLines(joined, key) {
|
|
9849
|
+
return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
|
|
9850
|
+
}
|
|
9851
|
+
function isKeyedPin(pin) {
|
|
9852
|
+
return pin.startsWith(KEYED_PIN_PREFIX);
|
|
9853
|
+
}
|
|
9854
|
+
function pinOfLines(lines, range) {
|
|
9855
|
+
if (range.start < 1 || range.end > lines.length || range.end < range.start) return void 0;
|
|
9856
|
+
return hashLines(lines.slice(range.start - 1, range.end).join("\n"));
|
|
9857
|
+
}
|
|
9858
|
+
var MOVE_WINDOW_LINES = 2e3;
|
|
9859
|
+
var MOVE_BUDGET_BYTES = 64 * 1024 * 1024;
|
|
9860
|
+
var MAX_RANGE_LINES = 5e3;
|
|
9861
|
+
function findWindows(lines, length, pin, key, opts) {
|
|
9862
|
+
const starts = [];
|
|
9863
|
+
const lastStart = lines.length - length + 1;
|
|
9864
|
+
if (length < 1 || lastStart < 1) return { starts, truncated: false };
|
|
9865
|
+
const joined = (start) => lines.slice(start - 1, start - 1 + length).join("\n");
|
|
9866
|
+
const original = opts?.original;
|
|
9867
|
+
if (original !== void 0) {
|
|
9868
|
+
const first = original[0];
|
|
9869
|
+
for (let start = 1; start <= lastStart; start++) {
|
|
9870
|
+
if (lines[start - 1] !== first) continue;
|
|
9871
|
+
let same = true;
|
|
9872
|
+
for (let i = 1; i < length; i++) {
|
|
9873
|
+
if (lines[start - 1 + i] !== original[i]) {
|
|
9874
|
+
same = false;
|
|
9875
|
+
break;
|
|
9876
|
+
}
|
|
9877
|
+
}
|
|
9878
|
+
if (same && hashLines(joined(start), key) === pin) starts.push(start);
|
|
9879
|
+
}
|
|
9880
|
+
return { starts, truncated: false };
|
|
9881
|
+
}
|
|
9882
|
+
const budget = opts?.budget ?? MOVE_BUDGET_BYTES;
|
|
9883
|
+
const around = opts?.around ?? 1;
|
|
9884
|
+
const bandStart = Math.max(1, around - MOVE_WINDOW_LINES);
|
|
9885
|
+
const bandEnd = Math.min(lastStart, around + MOVE_WINDOW_LINES);
|
|
9886
|
+
const order = [];
|
|
9887
|
+
for (let start = bandStart; start <= bandEnd; start++) order.push(start);
|
|
9888
|
+
for (let start = 1; start < bandStart; start++) order.push(start);
|
|
9889
|
+
for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
|
|
9890
|
+
let spent = 0;
|
|
9891
|
+
for (const start of order) {
|
|
9892
|
+
const text2 = joined(start);
|
|
9893
|
+
const bytes = Buffer.byteLength(text2, "utf8");
|
|
9894
|
+
if (spent + bytes > budget) return { starts, truncated: true };
|
|
9895
|
+
spent += bytes;
|
|
9896
|
+
if (hashLines(text2, key) === pin) starts.push(start);
|
|
9897
|
+
}
|
|
9898
|
+
return { starts, truncated: false };
|
|
9899
|
+
}
|
|
9900
|
+
var RANGE_SPEC = /^([1-9][0-9]*)(?:-([1-9][0-9]*))?$/;
|
|
9901
|
+
function parseLines(spec) {
|
|
9902
|
+
if (typeof spec === "number") {
|
|
9903
|
+
return Number.isInteger(spec) && spec >= 1 ? { start: spec, end: spec } : void 0;
|
|
9904
|
+
}
|
|
9905
|
+
const m = RANGE_SPEC.exec(spec);
|
|
9906
|
+
const startText = m?.[1];
|
|
9907
|
+
if (startText === void 0) return void 0;
|
|
9908
|
+
const start = Number(startText);
|
|
9909
|
+
const endText = m?.[2];
|
|
9910
|
+
const end = endText === void 0 ? start : Number(endText);
|
|
9911
|
+
return end < start ? void 0 : { start, end };
|
|
9912
|
+
}
|
|
9913
|
+
function lineSpec(lines) {
|
|
9914
|
+
return lines.start === lines.end ? lines.start : `${String(lines.start)}-${String(lines.end)}`;
|
|
9915
|
+
}
|
|
9916
|
+
function spellLines(lines) {
|
|
9917
|
+
return String(lineSpec(lines));
|
|
9918
|
+
}
|
|
9919
|
+
function toFileLines(lines, bodyLine) {
|
|
9920
|
+
return { start: bodyLine + lines.start - 1, end: bodyLine + lines.end - 1 };
|
|
9921
|
+
}
|
|
9922
|
+
function toBodyLines(lines, bodyLine) {
|
|
9923
|
+
return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
|
|
9924
|
+
}
|
|
9925
|
+
function splitPageArgument(arg) {
|
|
9926
|
+
const text2 = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
|
|
9927
|
+
const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text2);
|
|
9928
|
+
const head = m === null ? text2 : text2.slice(0, m.index);
|
|
9929
|
+
if (m === null || head === "") return { page: arg };
|
|
9930
|
+
const start = Number(m[1]);
|
|
9931
|
+
const end = m[2] === void 0 ? start : Number(m[2]);
|
|
9932
|
+
if (end < start) {
|
|
9933
|
+
throw new LineRangeError(`Invalid range "${arg}": end line ${end} is before start line ${start}.`);
|
|
9934
|
+
}
|
|
9935
|
+
return { page: head, lines: { start, end } };
|
|
9768
9936
|
}
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9937
|
+
|
|
9938
|
+
// src/meta/core/derive/provenance.ts
|
|
9939
|
+
import picomatch5 from "picomatch";
|
|
9940
|
+
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
9941
|
+
var DEFAULT_MACHINES = ["*[bot]"];
|
|
9942
|
+
var PROVENANCE_POINTER = "/provenance";
|
|
9943
|
+
var PLAIN_PIN = /^sha256-[0-9a-f]{64}$/;
|
|
9944
|
+
function provenanceEntries(value) {
|
|
9945
|
+
if (!Array.isArray(value)) return [];
|
|
9946
|
+
const out = [];
|
|
9947
|
+
for (const item of value) {
|
|
9948
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
9949
|
+
const record = item;
|
|
9950
|
+
const machine = record["generated-by"];
|
|
9951
|
+
const lines = record.lines;
|
|
9952
|
+
const integrity = record.integrity;
|
|
9953
|
+
if (typeof machine !== "string" || machine === "") continue;
|
|
9954
|
+
if (typeof lines !== "string" && typeof lines !== "number") continue;
|
|
9955
|
+
if (parseLines(lines) === void 0) continue;
|
|
9956
|
+
if (typeof integrity !== "string" || !PLAIN_PIN.test(integrity)) continue;
|
|
9957
|
+
out.push({ ...record, "generated-by": machine, lines, integrity });
|
|
9780
9958
|
}
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9959
|
+
return out;
|
|
9960
|
+
}
|
|
9961
|
+
function readProvenancePage(content, opts) {
|
|
9962
|
+
const lines = splitLines(content);
|
|
9963
|
+
const loc = opts?.fenced === false ? null : locateFrontmatter(content);
|
|
9964
|
+
if (loc === null) return { lines, bodyLine: 1, body: lines, stamp: [] };
|
|
9965
|
+
const head = content.slice(0, loc.closeEnd);
|
|
9966
|
+
const newlines = head.split("\n").length - 1;
|
|
9967
|
+
const bodyLine = newlines + (head.endsWith("\n") ? 1 : 2);
|
|
9968
|
+
let stamp = [];
|
|
9969
|
+
try {
|
|
9970
|
+
stamp = provenanceEntries(extractFrontmatter(content, "markdown").data.provenance);
|
|
9971
|
+
} catch {
|
|
9972
|
+
stamp = [];
|
|
9973
|
+
}
|
|
9974
|
+
return { lines, bodyLine, body: lines.slice(bodyLine - 1), stamp };
|
|
9975
|
+
}
|
|
9976
|
+
var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
|
|
9977
|
+
var ALL_ZERO = /^0+$/;
|
|
9978
|
+
function parseLinePorcelain(text2) {
|
|
9979
|
+
const rows = text2.split("\n");
|
|
9980
|
+
if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
|
|
9981
|
+
const out = [];
|
|
9982
|
+
let i = 0;
|
|
9983
|
+
while (i < rows.length) {
|
|
9984
|
+
const headerRow = rows[i] ?? "";
|
|
9985
|
+
const m = HEADER.exec(headerRow);
|
|
9986
|
+
const sha = m?.[1];
|
|
9987
|
+
const orig = m?.[2];
|
|
9988
|
+
const final = m?.[3];
|
|
9989
|
+
if (sha === void 0 || orig === void 0 || final === void 0) {
|
|
9990
|
+
throw new Error(`porcelain: expected a header at row ${i + 1}, got ${JSON.stringify(headerRow)}`);
|
|
9991
|
+
}
|
|
9992
|
+
const finalLine = Number(final);
|
|
9993
|
+
const count = m?.[4];
|
|
9994
|
+
const fields = /* @__PURE__ */ new Map();
|
|
9995
|
+
i++;
|
|
9996
|
+
for (let row = rows[i]; row !== void 0 && !row.startsWith(" "); row = rows[i]) {
|
|
9997
|
+
const space2 = row.indexOf(" ");
|
|
9998
|
+
if (space2 === -1) fields.set(row, true);
|
|
9999
|
+
else fields.set(row.slice(0, space2), row.slice(space2 + 1));
|
|
10000
|
+
i++;
|
|
9792
10001
|
}
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
10002
|
+
const contentRow = rows[i];
|
|
10003
|
+
if (contentRow === void 0) throw new Error(`porcelain: line ${finalLine} has no content row`);
|
|
10004
|
+
const author = fields.get("author");
|
|
10005
|
+
const filename = fields.get("filename");
|
|
10006
|
+
if (typeof author !== "string" || typeof filename !== "string") {
|
|
10007
|
+
throw new Error(`porcelain: line ${finalLine} has no full header; run blame with --line-porcelain`);
|
|
9796
10008
|
}
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
10009
|
+
const mail = fields.get("author-mail");
|
|
10010
|
+
const previous = fields.get("previous");
|
|
10011
|
+
const space = typeof previous === "string" ? previous.indexOf(" ") : -1;
|
|
10012
|
+
out.push({
|
|
10013
|
+
sha,
|
|
10014
|
+
origLine: Number(orig),
|
|
10015
|
+
finalLine,
|
|
10016
|
+
...count !== void 0 ? { groupCount: Number(count) } : {},
|
|
10017
|
+
author,
|
|
10018
|
+
authorMail: typeof mail === "string" ? mail.replace(/^<(.*)>$/, "$1") : "",
|
|
10019
|
+
filename,
|
|
10020
|
+
...typeof previous === "string" && space !== -1 ? { previous: { sha: previous.slice(0, space), filename: previous.slice(space + 1) } } : {},
|
|
10021
|
+
boundary: fields.get("boundary") === true,
|
|
10022
|
+
uncommitted: ALL_ZERO.test(sha),
|
|
10023
|
+
content: contentRow.slice(1)
|
|
10024
|
+
});
|
|
10025
|
+
i++;
|
|
10026
|
+
}
|
|
10027
|
+
return out;
|
|
10028
|
+
}
|
|
10029
|
+
var IDENTITY_GLOB = { literalBrackets: true, dot: true, bash: true };
|
|
10030
|
+
function machineIdentity(value, machines) {
|
|
10031
|
+
const m = /^(.*?)\s*<([^>]*)>\s*$/.exec(value);
|
|
10032
|
+
const name2 = m === null ? value.trim() : m[1] ?? "";
|
|
10033
|
+
const email = m === null ? "" : m[2] ?? "";
|
|
10034
|
+
const match = (s) => s !== "" && machines.some((glob) => picomatch5.isMatch(s, glob, IDENTITY_GLOB));
|
|
10035
|
+
if (!match(name2) && !match(email)) return void 0;
|
|
10036
|
+
return name2 !== "" ? name2 : email;
|
|
10037
|
+
}
|
|
10038
|
+
function stampEvidence(commit, origLine, fenced) {
|
|
10039
|
+
if (commit.blob === void 0) return void 0;
|
|
10040
|
+
const page = readProvenancePage(commit.blob, fenced === void 0 ? void 0 : { fenced });
|
|
10041
|
+
const body = origLine - page.bodyLine + 1;
|
|
10042
|
+
if (body < 1) return void 0;
|
|
10043
|
+
for (const entry of commit.stamp === void 0 ? page.stamp : provenanceEntries(commit.stamp)) {
|
|
10044
|
+
const span = parseLines(entry.lines);
|
|
10045
|
+
if (span === void 0 || body < span.start || body > span.end) continue;
|
|
10046
|
+
if (pinOfLines(page.body, span) === entry.integrity) return entry["generated-by"];
|
|
10047
|
+
}
|
|
10048
|
+
return void 0;
|
|
10049
|
+
}
|
|
10050
|
+
function recordedEvidence(line, ctx) {
|
|
10051
|
+
if (line.uncommitted) return { rule: 5, sha: line.sha };
|
|
10052
|
+
const commit = ctx.commits.get(line.sha);
|
|
10053
|
+
if (commit === void 0) throw new Error(`no commit ${line.sha}`);
|
|
10054
|
+
const stamped = stampEvidence(commit, line.origLine, ctx.fenced);
|
|
10055
|
+
if (stamped !== void 0) return { machine: stamped, rule: 2, sha: line.sha };
|
|
10056
|
+
const generated = commit.trailers.generatedBy.map((v) => v.trim()).find((v) => v !== "");
|
|
10057
|
+
if (generated !== void 0) return { machine: generated, rule: 3, sha: line.sha };
|
|
10058
|
+
for (const value of commit.trailers.coAuthoredBy) {
|
|
10059
|
+
const machine = machineIdentity(value.trim(), ctx.machines);
|
|
10060
|
+
if (machine !== void 0) return { machine, rule: 4, sha: line.sha };
|
|
10061
|
+
}
|
|
10062
|
+
return { rule: 5, sha: line.sha };
|
|
10063
|
+
}
|
|
10064
|
+
function resolveLineEvidence(line, ctx) {
|
|
10065
|
+
if (line.uncommitted && ctx.generatedBy !== void 0 && ctx.generatedBy !== "") {
|
|
10066
|
+
return { machine: ctx.generatedBy, rule: 1, sha: line.sha };
|
|
10067
|
+
}
|
|
10068
|
+
return recordedEvidence(line, ctx);
|
|
10069
|
+
}
|
|
10070
|
+
function pinOf(lines, span) {
|
|
10071
|
+
const pin = pinOfLines(lines, span);
|
|
10072
|
+
if (pin === void 0) throw new Error(`provenance: lines ${spellLines(span)} fall outside the body`);
|
|
10073
|
+
return pin;
|
|
10074
|
+
}
|
|
10075
|
+
function contextOf(input) {
|
|
10076
|
+
return {
|
|
10077
|
+
commits: input.commits,
|
|
10078
|
+
machines: input.machines ?? DEFAULT_MACHINES,
|
|
10079
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
10080
|
+
...input.fenced !== void 0 ? { fenced: input.fenced } : {}
|
|
10081
|
+
};
|
|
10082
|
+
}
|
|
10083
|
+
function readOptions(fenced) {
|
|
10084
|
+
return fenced === void 0 ? void 0 : { fenced };
|
|
10085
|
+
}
|
|
10086
|
+
function deriveProvenance(input) {
|
|
10087
|
+
const page = readProvenancePage(input.content, readOptions(input.fenced));
|
|
10088
|
+
const ctx = contextOf(input);
|
|
10089
|
+
const evidenceByLine = /* @__PURE__ */ new Map();
|
|
10090
|
+
for (const line of input.blame) {
|
|
10091
|
+
const body = line.finalLine - page.bodyLine + 1;
|
|
10092
|
+
if (body < 1) continue;
|
|
10093
|
+
evidenceByLine.set(body, resolveLineEvidence(line, ctx));
|
|
10094
|
+
}
|
|
10095
|
+
const groups = [];
|
|
10096
|
+
let open3;
|
|
10097
|
+
for (let n = 1; n <= page.body.length; n++) {
|
|
10098
|
+
const evidence = evidenceByLine.get(n);
|
|
10099
|
+
const machine = evidence?.machine;
|
|
10100
|
+
if (open3 !== void 0 && machine === open3.machine && n === open3.end + 1) {
|
|
10101
|
+
open3.end = n;
|
|
10102
|
+
continue;
|
|
10103
|
+
}
|
|
10104
|
+
if (open3 !== void 0) groups.push(open3);
|
|
10105
|
+
open3 = machine === void 0 || evidence === void 0 ? void 0 : { machine, start: n, end: n, first: evidence };
|
|
10106
|
+
}
|
|
10107
|
+
if (open3 !== void 0) groups.push(open3);
|
|
10108
|
+
return {
|
|
10109
|
+
page,
|
|
10110
|
+
evidenceByLine,
|
|
10111
|
+
derived: groups.map((g) => {
|
|
10112
|
+
const span = { start: g.start, end: g.end };
|
|
10113
|
+
return {
|
|
10114
|
+
entry: { "generated-by": g.machine, lines: lineSpec(span), integrity: pinOf(page.body, span) },
|
|
10115
|
+
span,
|
|
10116
|
+
evidence: g.first
|
|
10117
|
+
};
|
|
10118
|
+
})
|
|
10119
|
+
};
|
|
10120
|
+
}
|
|
10121
|
+
function nearest(candidates, recorded) {
|
|
10122
|
+
let best;
|
|
10123
|
+
for (const c of candidates) {
|
|
10124
|
+
const d = Math.abs(c.span.start - recorded.start);
|
|
10125
|
+
if (best === void 0 || d < best.d || d === best.d && c.span.start < best.c.span.start) best = { c, d };
|
|
10126
|
+
}
|
|
10127
|
+
return best?.c;
|
|
10128
|
+
}
|
|
10129
|
+
var sameSpan = (a, b) => a.start === b.start && a.end === b.end;
|
|
10130
|
+
function scanEvidence(evidenceByLine, span, machine) {
|
|
10131
|
+
let anyMachine = false;
|
|
10132
|
+
for (let n = span.start; n <= span.end; n++) {
|
|
10133
|
+
const evidence = evidenceByLine.get(n);
|
|
10134
|
+
if (evidence?.machine === void 0) continue;
|
|
10135
|
+
anyMachine = true;
|
|
10136
|
+
if (evidence.machine !== machine) return { contradiction: evidence, anyMachine };
|
|
10137
|
+
}
|
|
10138
|
+
return { anyMachine };
|
|
10139
|
+
}
|
|
10140
|
+
function compareProvenance(stamped, derivation) {
|
|
10141
|
+
const { page, evidenceByLine, derived } = derivation;
|
|
10142
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
10143
|
+
const results = [];
|
|
10144
|
+
for (const entry of provenanceEntries(stamped)) {
|
|
10145
|
+
const recorded = parseLines(entry.lines);
|
|
10146
|
+
if (recorded === void 0) continue;
|
|
10147
|
+
const machine = entry["generated-by"];
|
|
10148
|
+
const takenHere = [...claimed].some((d) => sameSpan(d.span, recorded));
|
|
10149
|
+
if (!takenHere && pinOfLines(page.body, recorded) === entry.integrity) {
|
|
10150
|
+
const here = scanEvidence(evidenceByLine, recorded, machine);
|
|
10151
|
+
if (here.contradiction === void 0) {
|
|
10152
|
+
const exact = derived.find(
|
|
10153
|
+
(d) => !claimed.has(d) && sameSpan(d.span, recorded) && d.entry.integrity === entry.integrity
|
|
10154
|
+
);
|
|
10155
|
+
if (exact !== void 0) claimed.add(exact);
|
|
10156
|
+
results.push({ status: "current", entry, span: recorded, ...here.anyMachine ? {} : { noEvidence: true } });
|
|
10157
|
+
continue;
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
const pool = derived.filter((d) => !claimed.has(d) && d.entry.integrity === entry.integrity);
|
|
10161
|
+
const hit = nearest(pool, recorded);
|
|
10162
|
+
if (hit !== void 0) {
|
|
10163
|
+
claimed.add(hit);
|
|
10164
|
+
if (hit.entry["generated-by"] !== machine) {
|
|
10165
|
+
results.push({ status: "stale", entry, span: hit.span, evidence: hit.evidence });
|
|
10166
|
+
} else {
|
|
10167
|
+
results.push({ status: sameSpan(hit.span, recorded) ? "current" : "moved", entry, span: hit.span });
|
|
10168
|
+
}
|
|
10169
|
+
continue;
|
|
10170
|
+
}
|
|
10171
|
+
const width = recorded.end - recorded.start + 1;
|
|
10172
|
+
const claimedSpans = [...claimed].map((d) => d.span);
|
|
10173
|
+
const windows = findWindows(page.body, width, entry.integrity, void 0, { around: recorded.start, budget: Infinity }).starts.map((start) => ({ span: { start, end: start + width - 1 } })).filter((w) => !claimedSpans.some((s) => sameSpan(s, w.span)));
|
|
10174
|
+
const found = nearest(windows, recorded);
|
|
10175
|
+
if (found === void 0) {
|
|
10176
|
+
results.push({ status: "changed", entry, span: recorded });
|
|
10177
|
+
continue;
|
|
10178
|
+
}
|
|
10179
|
+
const { contradiction, anyMachine } = scanEvidence(evidenceByLine, found.span, machine);
|
|
10180
|
+
if (contradiction !== void 0) {
|
|
10181
|
+
results.push({ status: "stale", entry, span: found.span, evidence: contradiction });
|
|
10182
|
+
continue;
|
|
10183
|
+
}
|
|
10184
|
+
results.push({
|
|
10185
|
+
status: sameSpan(found.span, recorded) ? "current" : "moved",
|
|
10186
|
+
entry,
|
|
10187
|
+
span: found.span,
|
|
10188
|
+
...anyMachine ? {} : { noEvidence: true }
|
|
10189
|
+
});
|
|
10190
|
+
}
|
|
10191
|
+
const covering = results.map((r) => r.span);
|
|
10192
|
+
for (const d of derived) {
|
|
10193
|
+
if (claimed.has(d)) continue;
|
|
10194
|
+
let uncovered = false;
|
|
10195
|
+
for (let n = d.span.start; n <= d.span.end && !uncovered; n++) {
|
|
10196
|
+
uncovered = !covering.some((s) => n >= s.start && n <= s.end);
|
|
10197
|
+
}
|
|
10198
|
+
if (uncovered) results.push({ status: "unset", entry: d.entry, span: d.span, evidence: d.evidence });
|
|
10199
|
+
}
|
|
10200
|
+
return results;
|
|
10201
|
+
}
|
|
10202
|
+
function planProvenanceWrite(comparisons, derivation) {
|
|
10203
|
+
const kept = [];
|
|
10204
|
+
for (const r of comparisons) {
|
|
10205
|
+
if (r.status === "current") kept.push({ entry: r.entry, start: r.span.start });
|
|
10206
|
+
else if (r.status === "moved") kept.push({ entry: { ...r.entry, lines: lineSpec(r.span) }, start: r.span.start });
|
|
10207
|
+
}
|
|
10208
|
+
const same = (a, b) => a["generated-by"] === b["generated-by"] && String(a.lines) === String(b.lines) && a.integrity === b.integrity;
|
|
10209
|
+
const out = [...kept];
|
|
10210
|
+
for (const d of derivation.derived) {
|
|
10211
|
+
if (!kept.some((k) => same(k.entry, d.entry))) out.push({ entry: d.entry, start: d.span.start });
|
|
10212
|
+
}
|
|
10213
|
+
return out.sort((a, b) => a.start - b.start).map((k) => k.entry);
|
|
10214
|
+
}
|
|
10215
|
+
var short = (sha) => sha.slice(0, 7);
|
|
10216
|
+
function fileSpelling(span, bodyLine) {
|
|
10217
|
+
return spellLines(toFileLines(span, bodyLine));
|
|
10218
|
+
}
|
|
10219
|
+
function evidenceText(evidence) {
|
|
10220
|
+
if (evidence?.machine === void 0) return "pin";
|
|
10221
|
+
return evidence.rule === 1 ? "uncommitted" : `blame ${short(evidence.sha)}`;
|
|
10222
|
+
}
|
|
10223
|
+
function evidenceRef(evidence) {
|
|
10224
|
+
if (evidence === void 0) return "no evidence";
|
|
10225
|
+
return evidence.rule === 1 ? "uncommitted" : short(evidence.sha);
|
|
10226
|
+
}
|
|
10227
|
+
function rangeResults(comparisons, bodyLine) {
|
|
10228
|
+
return comparisons.map((r) => {
|
|
10229
|
+
const lines = fileSpelling(r.span, bodyLine);
|
|
10230
|
+
const recorded = parseLines(r.entry.lines);
|
|
10231
|
+
const from = recorded === void 0 ? void 0 : fileSpelling(recorded, bodyLine);
|
|
10232
|
+
const machine = r.entry["generated-by"];
|
|
10233
|
+
switch (r.status) {
|
|
10234
|
+
case "stale":
|
|
10235
|
+
return {
|
|
10236
|
+
lines,
|
|
10237
|
+
"generated-by": r.evidence?.machine ?? machine,
|
|
10238
|
+
integrity: r.entry.integrity,
|
|
10239
|
+
status: r.status,
|
|
10240
|
+
evidence: evidenceText(r.evidence),
|
|
10241
|
+
...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
|
|
10242
|
+
written: false
|
|
10243
|
+
};
|
|
10244
|
+
case "moved":
|
|
10245
|
+
return {
|
|
10246
|
+
lines,
|
|
10247
|
+
"generated-by": machine,
|
|
10248
|
+
integrity: r.entry.integrity,
|
|
10249
|
+
status: r.status,
|
|
10250
|
+
evidence: "pin",
|
|
10251
|
+
...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
|
|
10252
|
+
written: false
|
|
10253
|
+
};
|
|
10254
|
+
case "unset":
|
|
10255
|
+
return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: evidenceText(r.evidence), written: false };
|
|
10256
|
+
case "current":
|
|
10257
|
+
case "changed":
|
|
10258
|
+
return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: "pin", written: false };
|
|
10259
|
+
}
|
|
10260
|
+
});
|
|
10261
|
+
}
|
|
10262
|
+
function provenanceFindings(comparisons, bodyLine) {
|
|
10263
|
+
const findings = [];
|
|
10264
|
+
for (const r of comparisons) {
|
|
10265
|
+
const machine = r.entry["generated-by"];
|
|
10266
|
+
const lines = fileSpelling(r.span, bodyLine);
|
|
10267
|
+
let message;
|
|
10268
|
+
switch (r.status) {
|
|
10269
|
+
case "changed":
|
|
10270
|
+
message = `provenance lines ${lines} changed since ${machine} wrote them`;
|
|
10271
|
+
break;
|
|
10272
|
+
case "stale":
|
|
10273
|
+
message = `provenance lines ${lines} say ${machine}; blame says ${r.evidence?.machine ?? "another machine"} (${evidenceRef(r.evidence)})`;
|
|
10274
|
+
break;
|
|
10275
|
+
case "unset":
|
|
10276
|
+
message = `provenance is unset for lines ${lines}; blame says ${machine} (${evidenceRef(r.evidence)})`;
|
|
10277
|
+
break;
|
|
10278
|
+
case "current":
|
|
10279
|
+
case "moved":
|
|
10280
|
+
continue;
|
|
10281
|
+
}
|
|
10282
|
+
findings.push({
|
|
10283
|
+
schema: DERIVED_STALE_SCHEMA,
|
|
10284
|
+
keyword: DERIVED_KEYWORD,
|
|
10285
|
+
subject: `provenance ${r.entry.integrity}`,
|
|
10286
|
+
instancePath: PROVENANCE_POINTER,
|
|
10287
|
+
message: `${message} \u2014 run manni meta derive`,
|
|
10288
|
+
line: toFileLines(r.span, bodyLine).start
|
|
10289
|
+
});
|
|
10290
|
+
}
|
|
10291
|
+
return findings;
|
|
10292
|
+
}
|
|
10293
|
+
function renderProvenanceValue(entries, bodyLine) {
|
|
10294
|
+
return entries.map((e) => {
|
|
10295
|
+
const span = parseLines(e.lines);
|
|
10296
|
+
const lines = span === void 0 ? String(e.lines) : fileSpelling(span, bodyLine);
|
|
10297
|
+
return `lines ${lines} ${e["generated-by"]}`;
|
|
10298
|
+
}).join("; ");
|
|
10299
|
+
}
|
|
10300
|
+
function parseProvenanceTarget(arg) {
|
|
10301
|
+
try {
|
|
10302
|
+
return splitPageArgument(arg);
|
|
10303
|
+
} catch (err) {
|
|
10304
|
+
if (err instanceof LineRangeError) throw new DocmetaError(`${arg} ends before it starts.`);
|
|
10305
|
+
throw err;
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
function attributeRange(input) {
|
|
10309
|
+
const { target } = input;
|
|
10310
|
+
const { page: path, lines: range } = parseProvenanceTarget(target);
|
|
10311
|
+
if (range === void 0) throw new Error(`provenance: ${target} names no lines`);
|
|
10312
|
+
const page = readProvenancePage(input.content, readOptions(input.fenced));
|
|
10313
|
+
if (range.end > page.lines.length) {
|
|
10314
|
+
throw new DocmetaError(
|
|
10315
|
+
`${path} has no lines ${spellLines(range)}: the file ends at line ${page.lines.length}.`
|
|
10316
|
+
);
|
|
10317
|
+
}
|
|
10318
|
+
if (range.start < page.bodyLine) {
|
|
10319
|
+
throw new DocmetaError(
|
|
10320
|
+
`${target} reaches into the frontmatter; provenance pins body lines, which start at line ${page.bodyLine}.`
|
|
10321
|
+
);
|
|
10322
|
+
}
|
|
10323
|
+
const ctx = contextOf(input);
|
|
10324
|
+
for (const line of input.blame) {
|
|
10325
|
+
if (line.finalLine < range.start || line.finalLine > range.end) continue;
|
|
10326
|
+
const evidence = recordedEvidence(line, ctx);
|
|
10327
|
+
if (evidence.machine !== void 0 && evidence.machine !== input.generatedBy) {
|
|
10328
|
+
throw new DocmetaError(
|
|
10329
|
+
`${target}: blame attributes these lines to ${evidence.machine} (${short(evidence.sha)}); --generated-by cannot overrule a recorded machine.`
|
|
10330
|
+
);
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
const span = toBodyLines(range, page.bodyLine);
|
|
10334
|
+
return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
|
|
10335
|
+
}
|
|
10336
|
+
|
|
10337
|
+
// src/meta/core/derive/git.ts
|
|
10338
|
+
var DEFAULT_BULK_THRESHOLD = 32;
|
|
10339
|
+
var NULL_SHA = "0".repeat(40);
|
|
10340
|
+
var MAX_GIT_OUTPUT_BYTES = Math.min(
|
|
10341
|
+
512 * 1024 * 1024,
|
|
10342
|
+
bufferConstants.MAX_STRING_LENGTH
|
|
10343
|
+
);
|
|
10344
|
+
var HistoryTooLarge = class extends Error {
|
|
10345
|
+
};
|
|
10346
|
+
var BlobsUnreadable = class extends Error {
|
|
10347
|
+
constructor(detail) {
|
|
10348
|
+
super(detail);
|
|
10349
|
+
this.detail = detail;
|
|
10350
|
+
}
|
|
10351
|
+
detail;
|
|
10352
|
+
};
|
|
10353
|
+
var BlameUnreadable = class extends Error {
|
|
10354
|
+
constructor(rel, detail) {
|
|
10355
|
+
super(rel);
|
|
10356
|
+
this.rel = rel;
|
|
10357
|
+
this.detail = detail;
|
|
10358
|
+
}
|
|
10359
|
+
rel;
|
|
10360
|
+
detail;
|
|
10361
|
+
};
|
|
10362
|
+
function bodyOf(content, fenced) {
|
|
10363
|
+
if (fenced !== true) return content;
|
|
10364
|
+
const loc = locateFrontmatter(content);
|
|
10365
|
+
if (!loc) return content;
|
|
10366
|
+
return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
|
|
10367
|
+
}
|
|
10368
|
+
async function deriveFromGit(inputs, opts) {
|
|
10369
|
+
const records = /* @__PURE__ */ new Map();
|
|
10370
|
+
const byRoot = groupByRoot(inputs);
|
|
10371
|
+
if (byRoot.size === 0) {
|
|
10372
|
+
return {
|
|
10373
|
+
status: {
|
|
10374
|
+
available: false,
|
|
10375
|
+
reason: "no git repository contains the documents"
|
|
10376
|
+
},
|
|
10377
|
+
records
|
|
10378
|
+
};
|
|
10379
|
+
}
|
|
10380
|
+
const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
|
|
10381
|
+
const machines = opts.machines ?? DEFAULT_MACHINES;
|
|
10382
|
+
const wantsProvenance = opts.fields?.includes(PROVENANCE_FIELD) ?? false;
|
|
10383
|
+
const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
|
|
10384
|
+
const reasons = [];
|
|
10385
|
+
for (const [root, bucket] of byRoot) {
|
|
10386
|
+
const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
|
|
10387
|
+
const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
|
|
10388
|
+
if (shallow.missing) {
|
|
10389
|
+
return {
|
|
10390
|
+
status: { available: false, reason: "git is not on PATH" },
|
|
10391
|
+
records: /* @__PURE__ */ new Map()
|
|
10392
|
+
};
|
|
10393
|
+
}
|
|
10394
|
+
if (shallow.code !== 0) {
|
|
10395
|
+
reasons.push(`git could not read the repository at ${root}`);
|
|
10396
|
+
continue;
|
|
10397
|
+
}
|
|
10398
|
+
if (text(shallow).trim() === "true") {
|
|
10399
|
+
reasons.push(
|
|
10400
|
+
`this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
|
|
10401
|
+
);
|
|
9801
10402
|
continue;
|
|
9802
10403
|
}
|
|
9803
|
-
let histories;
|
|
9804
|
-
let blobs;
|
|
9805
10404
|
try {
|
|
9806
|
-
histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
|
|
10405
|
+
const histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
|
|
9807
10406
|
if (histories === null) {
|
|
9808
10407
|
reasons.push(`git could not read the history at ${root}`);
|
|
9809
10408
|
continue;
|
|
9810
10409
|
}
|
|
9811
|
-
blobs = await fetchBlobs(run2, neededBlobs(histories));
|
|
10410
|
+
const blobs = await fetchBlobs(run2, neededBlobs(histories));
|
|
10411
|
+
for (const entry of bucket) {
|
|
10412
|
+
const history = histories.get(entry.rel) ?? [];
|
|
10413
|
+
const facts = judge(entry.input, root, history, blobs, opts.now, machines);
|
|
10414
|
+
if (wantsProvenance) {
|
|
10415
|
+
const derived = await provenanceFor(run2, root, entry, history, blobs, {
|
|
10416
|
+
machines,
|
|
10417
|
+
...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
|
|
10418
|
+
});
|
|
10419
|
+
facts.provenance = derived.value;
|
|
10420
|
+
facts.provenanceDerivation = derived.derivation;
|
|
10421
|
+
}
|
|
10422
|
+
records.set(entry.input.label, facts);
|
|
10423
|
+
}
|
|
9812
10424
|
} catch (err) {
|
|
9813
10425
|
if (err instanceof BlobsUnreadable) {
|
|
9814
10426
|
reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
|
|
9815
10427
|
continue;
|
|
9816
10428
|
}
|
|
10429
|
+
if (err instanceof BlameUnreadable) {
|
|
10430
|
+
const why = err.detail === "" ? "" : `: ${err.detail}`;
|
|
10431
|
+
reasons.push(`git blame could not read ${err.rel} (${root})${why}`);
|
|
10432
|
+
continue;
|
|
10433
|
+
}
|
|
9817
10434
|
if (!(err instanceof HistoryTooLarge)) throw err;
|
|
9818
10435
|
reasons.push(`git history is too large to read in one pass (${root})`);
|
|
9819
10436
|
continue;
|
|
9820
10437
|
}
|
|
9821
|
-
for (const entry of bucket) {
|
|
9822
|
-
const history = histories.get(entry.rel) ?? [];
|
|
9823
|
-
records.set(
|
|
9824
|
-
entry.input.label,
|
|
9825
|
-
judge(entry.input, root, history, blobs, opts.now)
|
|
9826
|
-
);
|
|
9827
|
-
}
|
|
9828
10438
|
}
|
|
9829
10439
|
if (reasons.length > 0) {
|
|
9830
10440
|
return {
|
|
@@ -9852,7 +10462,7 @@ function groupByRoot(inputs) {
|
|
|
9852
10462
|
}
|
|
9853
10463
|
return byRoot;
|
|
9854
10464
|
}
|
|
9855
|
-
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)";
|
|
10465
|
+
var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)";
|
|
9856
10466
|
var LOG_ARGS = [
|
|
9857
10467
|
"-c",
|
|
9858
10468
|
"core.quotePath=false",
|
|
@@ -9918,6 +10528,7 @@ function parseLog(text2) {
|
|
|
9918
10528
|
authorEmail: fields[3] ?? "",
|
|
9919
10529
|
coAuthors: splitTrailers(fields[4]),
|
|
9920
10530
|
reviewedBy: splitTrailers(fields[5]),
|
|
10531
|
+
generatedBy: splitTrailers(fields[6]),
|
|
9921
10532
|
raw
|
|
9922
10533
|
});
|
|
9923
10534
|
}
|
|
@@ -10008,6 +10619,7 @@ function attribute(records, wanted) {
|
|
|
10008
10619
|
authorEmail: record.authorEmail,
|
|
10009
10620
|
coAuthors: record.coAuthors,
|
|
10010
10621
|
reviewedBy: record.reviewedBy,
|
|
10622
|
+
generatedBy: record.generatedBy,
|
|
10011
10623
|
oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
|
|
10012
10624
|
newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
|
|
10013
10625
|
pathAtCommit: entry.newPath
|
|
@@ -10057,7 +10669,7 @@ var git = (value, evidence) => ({
|
|
|
10057
10669
|
source: "git",
|
|
10058
10670
|
evidence
|
|
10059
10671
|
});
|
|
10060
|
-
function judge(input, root, history, blobs, now) {
|
|
10672
|
+
function judge(input, root, history, blobs, now, machines) {
|
|
10061
10673
|
const fenced = input.extracted.fenced;
|
|
10062
10674
|
const extractor = extractorFor(input);
|
|
10063
10675
|
const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
|
|
@@ -10094,11 +10706,11 @@ function judge(input, root, history, blobs, now) {
|
|
|
10094
10706
|
stampsOf(newestBody.oldBlob)
|
|
10095
10707
|
);
|
|
10096
10708
|
}
|
|
10097
|
-
const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging);
|
|
10709
|
+
const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
|
|
10098
10710
|
let reviewedBy = null;
|
|
10099
10711
|
let lastReviewed = null;
|
|
10100
10712
|
if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
|
|
10101
|
-
const evidence = `Reviewed-by trailer in ${
|
|
10713
|
+
const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
|
|
10102
10714
|
reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
|
|
10103
10715
|
lastReviewed = git(datePart(newestBody.authorDate), evidence);
|
|
10104
10716
|
}
|
|
@@ -10108,6 +10720,7 @@ function judge(input, root, history, blobs, now) {
|
|
|
10108
10720
|
authors,
|
|
10109
10721
|
"reviewed-by": reviewedBy,
|
|
10110
10722
|
"last-reviewed": lastReviewed,
|
|
10723
|
+
provenance: null,
|
|
10111
10724
|
lastBodyCommit,
|
|
10112
10725
|
root
|
|
10113
10726
|
};
|
|
@@ -10115,24 +10728,26 @@ function judge(input, root, history, blobs, now) {
|
|
|
10115
10728
|
function createdOf(oldest, stamps) {
|
|
10116
10729
|
const stamp = stamps["created"];
|
|
10117
10730
|
if (stamp !== void 0 && stamp !== null) {
|
|
10118
|
-
return git(stamp, `stamped in ${
|
|
10731
|
+
return git(stamp, `stamped in ${short2(oldest.sha)}`);
|
|
10119
10732
|
}
|
|
10120
10733
|
const date = datePart(oldest.authorDate);
|
|
10121
|
-
return git(date, `added in ${
|
|
10734
|
+
return git(date, `added in ${short2(oldest.sha)} (${date})`);
|
|
10122
10735
|
}
|
|
10123
10736
|
function lastUpdatedOf(commit, after, before) {
|
|
10124
10737
|
const stamp = after["last-updated"];
|
|
10125
10738
|
if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before["last-updated"])) {
|
|
10126
|
-
return git(stamp, `stamped in ${
|
|
10739
|
+
return git(stamp, `stamped in ${short2(commit.sha)}`);
|
|
10127
10740
|
}
|
|
10128
10741
|
const date = datePart(commit.authorDate);
|
|
10129
|
-
return git(date, `body changed in ${
|
|
10742
|
+
return git(date, `body changed in ${short2(commit.sha)} (${date})`);
|
|
10130
10743
|
}
|
|
10131
|
-
function authorsOf(bodyChanging) {
|
|
10744
|
+
function authorsOf(bodyChanging, machines) {
|
|
10132
10745
|
const seen = /* @__PURE__ */ new Set();
|
|
10133
10746
|
const names = [];
|
|
10134
10747
|
const add = (name2, email) => {
|
|
10135
|
-
if (name2 === ""
|
|
10748
|
+
if (name2 === "") return;
|
|
10749
|
+
const identity = email === null || email === "" ? name2 : `${name2} <${email}>`;
|
|
10750
|
+
if (machineIdentity(identity, machines) !== void 0) return;
|
|
10136
10751
|
const key = email === null || email === "" ? `name:${name2.toLowerCase()}` : email.toLowerCase();
|
|
10137
10752
|
if (seen.has(key)) return;
|
|
10138
10753
|
seen.add(key);
|
|
@@ -10145,6 +10760,204 @@ function authorsOf(bodyChanging) {
|
|
|
10145
10760
|
const n = bodyChanging.length;
|
|
10146
10761
|
return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
|
|
10147
10762
|
}
|
|
10763
|
+
function provenanceFenced(extracted) {
|
|
10764
|
+
return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
|
|
10765
|
+
}
|
|
10766
|
+
var OUTSIDE_RANGE = "outside-the-attributed-range";
|
|
10767
|
+
var ATTRIBUTED = ":attributed";
|
|
10768
|
+
async function provenanceFor(run2, root, entry, history, blobs, opts) {
|
|
10769
|
+
const { input, rel } = entry;
|
|
10770
|
+
const fenced = provenanceFenced(input.extracted);
|
|
10771
|
+
const blame = await blameOf(run2, rel, input.content);
|
|
10772
|
+
const commits = await commitEvidence(run2, root, entry, blame, history, blobs, input.provenanceManifest);
|
|
10773
|
+
const base = { content: input.content, commits, machines: opts.machines, fenced };
|
|
10774
|
+
let derivation;
|
|
10775
|
+
const attribution = input.attribution;
|
|
10776
|
+
if (attribution !== void 0) {
|
|
10777
|
+
const ranges = attribution.targets.flatMap((target) => {
|
|
10778
|
+
attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy });
|
|
10779
|
+
const range = parseProvenanceTarget(target).lines;
|
|
10780
|
+
return range === void 0 ? [] : [range];
|
|
10781
|
+
});
|
|
10782
|
+
const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
|
|
10783
|
+
const attributed = new Map(commits);
|
|
10784
|
+
const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
|
|
10785
|
+
attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
|
|
10786
|
+
const rekeyed = blame.map((line) => {
|
|
10787
|
+
if (inRange(line)) {
|
|
10788
|
+
if (line.uncommitted) return line;
|
|
10789
|
+
const sha = `${line.sha}${ATTRIBUTED}`;
|
|
10790
|
+
attributed.set(sha, { sha, trailers });
|
|
10791
|
+
return { ...line, sha };
|
|
10792
|
+
}
|
|
10793
|
+
return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
|
|
10794
|
+
});
|
|
10795
|
+
derivation = deriveProvenance({
|
|
10796
|
+
...base,
|
|
10797
|
+
commits: attributed,
|
|
10798
|
+
generatedBy: attribution.generatedBy,
|
|
10799
|
+
blame: rekeyed
|
|
10800
|
+
});
|
|
10801
|
+
} else {
|
|
10802
|
+
derivation = deriveProvenance({
|
|
10803
|
+
...base,
|
|
10804
|
+
blame,
|
|
10805
|
+
...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
|
|
10806
|
+
});
|
|
10807
|
+
}
|
|
10808
|
+
const entries = derivation.derived.map((d) => d.entry);
|
|
10809
|
+
return {
|
|
10810
|
+
value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
|
|
10811
|
+
derivation
|
|
10812
|
+
};
|
|
10813
|
+
}
|
|
10814
|
+
function provenanceEvidence(derivation) {
|
|
10815
|
+
const shas = [];
|
|
10816
|
+
let uncommitted = false;
|
|
10817
|
+
for (const range of derivation.derived) {
|
|
10818
|
+
for (let n = range.span.start; n <= range.span.end; n++) {
|
|
10819
|
+
const evidence = derivation.evidenceByLine.get(n);
|
|
10820
|
+
if (evidence?.machine === void 0) continue;
|
|
10821
|
+
if (evidence.rule === 1) uncommitted = true;
|
|
10822
|
+
else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
|
|
10823
|
+
}
|
|
10824
|
+
}
|
|
10825
|
+
const first = shas[0];
|
|
10826
|
+
if (first === void 0) return "uncommitted";
|
|
10827
|
+
const parts = [`blame ${short2(first)}`];
|
|
10828
|
+
if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
|
|
10829
|
+
if (uncommitted) parts.push("uncommitted");
|
|
10830
|
+
return parts.join(", ");
|
|
10831
|
+
}
|
|
10832
|
+
async function blameOf(run2, rel, content) {
|
|
10833
|
+
const out = await run2([
|
|
10834
|
+
"-c",
|
|
10835
|
+
"core.quotePath=false",
|
|
10836
|
+
"blame",
|
|
10837
|
+
"--no-ignore-revs-file",
|
|
10838
|
+
"--line-porcelain",
|
|
10839
|
+
"--",
|
|
10840
|
+
rel
|
|
10841
|
+
]);
|
|
10842
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
10843
|
+
if (out.code !== 0) {
|
|
10844
|
+
const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
|
|
10845
|
+
if (inHead.code !== 0) {
|
|
10846
|
+
return splitLines(content).map((line, i) => ({
|
|
10847
|
+
sha: ZERO_SHA,
|
|
10848
|
+
origLine: i + 1,
|
|
10849
|
+
finalLine: i + 1,
|
|
10850
|
+
author: "Not Committed Yet",
|
|
10851
|
+
authorMail: "not.committed.yet",
|
|
10852
|
+
filename: rel,
|
|
10853
|
+
boundary: false,
|
|
10854
|
+
uncommitted: true,
|
|
10855
|
+
content: line
|
|
10856
|
+
}));
|
|
10857
|
+
}
|
|
10858
|
+
throw new BlameUnreadable(rel, out.stderr.trim());
|
|
10859
|
+
}
|
|
10860
|
+
return parseLinePorcelain(text(out));
|
|
10861
|
+
}
|
|
10862
|
+
async function commitEvidence(run2, root, entry, blame, history, blobs, manifest) {
|
|
10863
|
+
const pathAt = /* @__PURE__ */ new Map();
|
|
10864
|
+
for (const line of blame) {
|
|
10865
|
+
if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
|
|
10866
|
+
}
|
|
10867
|
+
const shas = [...pathAt.keys()];
|
|
10868
|
+
const commits = /* @__PURE__ */ new Map();
|
|
10869
|
+
if (shas.length === 0) return commits;
|
|
10870
|
+
const trailers = /* @__PURE__ */ new Map();
|
|
10871
|
+
const pageBlob = /* @__PURE__ */ new Map();
|
|
10872
|
+
for (const h of history) {
|
|
10873
|
+
trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
|
|
10874
|
+
const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
|
|
10875
|
+
if (blob !== void 0) pageBlob.set(h.sha, blob);
|
|
10876
|
+
}
|
|
10877
|
+
const unlogged = shas.filter((sha) => !trailers.has(sha));
|
|
10878
|
+
if (unlogged.length > 0) {
|
|
10879
|
+
const out = await run2(["log", "--no-walk=unsorted", RECORD_FORMAT, ...unlogged]);
|
|
10880
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
10881
|
+
if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
|
|
10882
|
+
for (const record of parseLog(text(out))) {
|
|
10883
|
+
trailers.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
|
|
10887
|
+
const specs = [];
|
|
10888
|
+
for (const sha of shas) {
|
|
10889
|
+
if (!pageBlob.has(sha)) specs.push(`${sha}:${pathAt.get(sha) ?? ""}`);
|
|
10890
|
+
if (manifestRel !== void 0) specs.push(`${sha}:${manifestRel}`);
|
|
10891
|
+
}
|
|
10892
|
+
const fetched = await fetchSpecs(run2, specs);
|
|
10893
|
+
for (const sha of shas) {
|
|
10894
|
+
const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${pathAt.get(sha) ?? ""}`);
|
|
10895
|
+
commits.set(sha, {
|
|
10896
|
+
sha,
|
|
10897
|
+
trailers: trailers.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
|
|
10898
|
+
...blob !== void 0 ? { blob } : {},
|
|
10899
|
+
...manifest !== void 0 ? {
|
|
10900
|
+
stamp: manifestRel === void 0 ? [] : manifestStamp(
|
|
10901
|
+
fetched.get(`${sha}:${manifestRel}`),
|
|
10902
|
+
manifest.join === "path" ? entryAt(manifest.entry, entry.rel, pathAt.get(sha) ?? entry.rel) : manifest.entry,
|
|
10903
|
+
manifest.join
|
|
10904
|
+
)
|
|
10905
|
+
} : {}
|
|
10906
|
+
});
|
|
10907
|
+
}
|
|
10908
|
+
return commits;
|
|
10909
|
+
}
|
|
10910
|
+
function insideRoot(root, abs) {
|
|
10911
|
+
const rel = relative10(root, abs);
|
|
10912
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute5(rel)) return void 0;
|
|
10913
|
+
return rel.split(sep5).join("/");
|
|
10914
|
+
}
|
|
10915
|
+
function entryAt(entry, rel, relAtCommit) {
|
|
10916
|
+
if (relAtCommit === rel) return entry;
|
|
10917
|
+
const fromHere = posix2.relative(posix2.dirname(rel), relAtCommit);
|
|
10918
|
+
return posix2.join(posix2.dirname(entry), fromHere);
|
|
10919
|
+
}
|
|
10920
|
+
function manifestStamp(text2, key, join10) {
|
|
10921
|
+
if (text2 === void 0) return [];
|
|
10922
|
+
let data;
|
|
10923
|
+
try {
|
|
10924
|
+
data = parseYaml4(text2);
|
|
10925
|
+
} catch {
|
|
10926
|
+
return [];
|
|
10927
|
+
}
|
|
10928
|
+
if (!isRecord4(data)) return [];
|
|
10929
|
+
for (const [candidate, value] of Object.entries(data)) {
|
|
10930
|
+
const same = join10 === "path" ? posix2.normalize(candidate) === posix2.normalize(key) : candidate === key;
|
|
10931
|
+
if (same && isRecord4(value)) return provenanceEntries(value[PROVENANCE_FIELD]);
|
|
10932
|
+
}
|
|
10933
|
+
return [];
|
|
10934
|
+
}
|
|
10935
|
+
function isRecord4(x) {
|
|
10936
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
10937
|
+
}
|
|
10938
|
+
async function fetchSpecs(run2, specs) {
|
|
10939
|
+
const found = /* @__PURE__ */ new Map();
|
|
10940
|
+
if (specs.length === 0) return found;
|
|
10941
|
+
const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
|
|
10942
|
+
`);
|
|
10943
|
+
if (out.tooLarge) throw new HistoryTooLarge();
|
|
10944
|
+
if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
|
|
10945
|
+
const buf = out.raw;
|
|
10946
|
+
let cursor = 0;
|
|
10947
|
+
for (const spec of specs) {
|
|
10948
|
+
const nl = buf.indexOf(10, cursor);
|
|
10949
|
+
if (nl === -1) break;
|
|
10950
|
+
const header = buf.toString("utf8", cursor, nl);
|
|
10951
|
+
cursor = nl + 1;
|
|
10952
|
+
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
|
|
10953
|
+
const parts = header.split(" ");
|
|
10954
|
+
const size = Number(parts[2]);
|
|
10955
|
+
if (!Number.isFinite(size)) break;
|
|
10956
|
+
if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
|
|
10957
|
+
cursor += size + 1;
|
|
10958
|
+
}
|
|
10959
|
+
return found;
|
|
10960
|
+
}
|
|
10148
10961
|
function extractorFor(input) {
|
|
10149
10962
|
return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
|
|
10150
10963
|
}
|
|
@@ -10163,7 +10976,7 @@ function sameBody(a, b) {
|
|
|
10163
10976
|
function sameValue(a, b) {
|
|
10164
10977
|
return toJsonText(a) === toJsonText(b);
|
|
10165
10978
|
}
|
|
10166
|
-
var
|
|
10979
|
+
var short2 = (sha) => sha.slice(0, 7);
|
|
10167
10980
|
var datePart = (iso) => iso.slice(0, 10);
|
|
10168
10981
|
function localDate(d) {
|
|
10169
10982
|
const pad2 = (n) => String(n).padStart(2, "0");
|
|
@@ -10177,6 +10990,7 @@ function emailOf(trailer) {
|
|
|
10177
10990
|
const m = /<([^>]*)>\s*$/.exec(trailer);
|
|
10178
10991
|
return m?.[1]?.trim() ?? null;
|
|
10179
10992
|
}
|
|
10993
|
+
var MAX_STDERR_BYTES = 16 * 1024;
|
|
10180
10994
|
function text(out) {
|
|
10181
10995
|
try {
|
|
10182
10996
|
return out.raw.toString("utf8");
|
|
@@ -10192,7 +11006,7 @@ function runGit(args, cwd, stdin, maxBytes) {
|
|
|
10192
11006
|
done = true;
|
|
10193
11007
|
settle(out);
|
|
10194
11008
|
};
|
|
10195
|
-
const
|
|
11009
|
+
const fail2 = (err) => {
|
|
10196
11010
|
if (done) return;
|
|
10197
11011
|
done = true;
|
|
10198
11012
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -10201,7 +11015,8 @@ function runGit(args, cwd, stdin, maxBytes) {
|
|
|
10201
11015
|
code: null,
|
|
10202
11016
|
raw: Buffer.alloc(0),
|
|
10203
11017
|
missing,
|
|
10204
|
-
tooLarge: false
|
|
11018
|
+
tooLarge: false,
|
|
11019
|
+
stderr: ""
|
|
10205
11020
|
});
|
|
10206
11021
|
let child;
|
|
10207
11022
|
try {
|
|
@@ -10224,7 +11039,14 @@ function runGit(args, cwd, stdin, maxBytes) {
|
|
|
10224
11039
|
}
|
|
10225
11040
|
chunks.push(chunk);
|
|
10226
11041
|
});
|
|
10227
|
-
|
|
11042
|
+
const errChunks = [];
|
|
11043
|
+
let errTotal = 0;
|
|
11044
|
+
child.stderr.on("data", (chunk) => {
|
|
11045
|
+
if (errTotal >= MAX_STDERR_BYTES) return;
|
|
11046
|
+
const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
|
|
11047
|
+
errTotal += kept.length;
|
|
11048
|
+
errChunks.push(kept);
|
|
11049
|
+
});
|
|
10228
11050
|
child.on("error", (err) => {
|
|
10229
11051
|
finish(failed(err.code === "ENOENT"));
|
|
10230
11052
|
});
|
|
@@ -10233,9 +11055,10 @@ function runGit(args, cwd, stdin, maxBytes) {
|
|
|
10233
11055
|
child.on("close", (code) => {
|
|
10234
11056
|
try {
|
|
10235
11057
|
const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
|
|
10236
|
-
|
|
11058
|
+
const stderr = Buffer.concat(errChunks).toString("utf8");
|
|
11059
|
+
finish({ code, raw, missing: false, tooLarge, stderr });
|
|
10237
11060
|
} catch (err) {
|
|
10238
|
-
|
|
11061
|
+
fail2(err);
|
|
10239
11062
|
}
|
|
10240
11063
|
});
|
|
10241
11064
|
if (stdin === void 0) child.stdin.end();
|
|
@@ -10250,7 +11073,8 @@ var FIELD_SOURCES = {
|
|
|
10250
11073
|
authors: ["git"],
|
|
10251
11074
|
owner: ["codeowners"],
|
|
10252
11075
|
"reviewed-by": ["github", "gitlab", "git"],
|
|
10253
|
-
"last-reviewed": ["github", "gitlab", "git"]
|
|
11076
|
+
"last-reviewed": ["github", "gitlab", "git"],
|
|
11077
|
+
provenance: ["git"]
|
|
10254
11078
|
};
|
|
10255
11079
|
var REVIEW_SOURCES = ["github", "gitlab"];
|
|
10256
11080
|
var HOST_NAME = {
|
|
@@ -10284,10 +11108,20 @@ async function deriveMetadata(inputs, ctx) {
|
|
|
10284
11108
|
const sources = {};
|
|
10285
11109
|
let git2 = /* @__PURE__ */ new Map();
|
|
10286
11110
|
if (consulted.has("git")) {
|
|
10287
|
-
const result = await deriveFromGit(inputs, {
|
|
11111
|
+
const result = await deriveFromGit(inputs, {
|
|
11112
|
+
cwd: ctx.cwd,
|
|
11113
|
+
now: ctx.now,
|
|
11114
|
+
fields: ctx.fields,
|
|
11115
|
+
...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
|
|
11116
|
+
...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
|
|
11117
|
+
});
|
|
10288
11118
|
sources.git = result.status;
|
|
10289
11119
|
git2 = result.records;
|
|
10290
11120
|
}
|
|
11121
|
+
const provenance = /* @__PURE__ */ new Map();
|
|
11122
|
+
for (const [label, facts] of git2) {
|
|
11123
|
+
if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
|
|
11124
|
+
}
|
|
10291
11125
|
const rootCache = /* @__PURE__ */ new Map();
|
|
10292
11126
|
const rootOf = (absPath) => {
|
|
10293
11127
|
const dir = dirname9(absPath);
|
|
@@ -10365,7 +11199,7 @@ async function deriveMetadata(inputs, ctx) {
|
|
|
10365
11199
|
}
|
|
10366
11200
|
records.set(input.label, { file: input.label, fields });
|
|
10367
11201
|
}
|
|
10368
|
-
return { records, sources };
|
|
11202
|
+
return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
|
|
10369
11203
|
}
|
|
10370
11204
|
function valueFrom(source, field, git2, reviews, owner, commanded) {
|
|
10371
11205
|
switch (source) {
|
|
@@ -10448,6 +11282,9 @@ function commandsOf(config) {
|
|
|
10448
11282
|
}
|
|
10449
11283
|
return out;
|
|
10450
11284
|
}
|
|
11285
|
+
function machinesOf(config) {
|
|
11286
|
+
return config?.machines ?? DEFAULT_MACHINES;
|
|
11287
|
+
}
|
|
10451
11288
|
|
|
10452
11289
|
// src/meta/core/derive/table.ts
|
|
10453
11290
|
var DERIVED_VIEW = "derived";
|
|
@@ -10462,10 +11299,11 @@ function mentionsDerived(sql) {
|
|
|
10462
11299
|
return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
|
|
10463
11300
|
}
|
|
10464
11301
|
function fieldsForSql(sql, fields) {
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
(field) =>
|
|
10468
|
-
|
|
11302
|
+
const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
|
|
11303
|
+
if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
|
|
11304
|
+
return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
|
|
11305
|
+
}
|
|
11306
|
+
return fields.filter(named);
|
|
10469
11307
|
}
|
|
10470
11308
|
function escapeRegExp(s) {
|
|
10471
11309
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -10559,16 +11397,18 @@ function createResolvedView(db, docsColumns, commands) {
|
|
|
10559
11397
|
async function deriveForTable(inputs, ctx, hint, fields) {
|
|
10560
11398
|
const derive = ctx.config?.derive;
|
|
10561
11399
|
const commands = commandsOf(derive);
|
|
11400
|
+
const managesProvenance = derive?.fields.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
|
|
10562
11401
|
const result = await deriveMetadata(inputs, {
|
|
10563
11402
|
cwd: ctx.cwd,
|
|
10564
11403
|
base: ctx.base,
|
|
10565
11404
|
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
10566
11405
|
sources: derive?.sources ?? [...DERIVE_SOURCES],
|
|
10567
|
-
fields,
|
|
11406
|
+
fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
|
|
10568
11407
|
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
10569
11408
|
...commands !== void 0 ? { commands } : {},
|
|
10570
11409
|
cache: ctx.cache,
|
|
10571
|
-
now: ctx.now ?? (() => /* @__PURE__ */ new Date())
|
|
11410
|
+
now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
|
|
11411
|
+
machines: machinesOf(derive)
|
|
10572
11412
|
});
|
|
10573
11413
|
assertSourcesAvailable(result.sources, hint);
|
|
10574
11414
|
return result.records;
|
|
@@ -10576,7 +11416,7 @@ async function deriveForTable(inputs, ctx, hint, fields) {
|
|
|
10576
11416
|
|
|
10577
11417
|
// src/meta/commands/validate.ts
|
|
10578
11418
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
10579
|
-
import { resolve as
|
|
11419
|
+
import { resolve as resolve13, extname as extname3 } from "node:path";
|
|
10580
11420
|
|
|
10581
11421
|
// src/meta/core/checks.ts
|
|
10582
11422
|
var CHECK_KEYWORD = "check";
|
|
@@ -10713,6 +11553,71 @@ async function runChecks(checks, entries, ctx = {}) {
|
|
|
10713
11553
|
return merged;
|
|
10714
11554
|
}
|
|
10715
11555
|
|
|
11556
|
+
// src/meta/core/derive/provenance-place.ts
|
|
11557
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep6 } from "node:path";
|
|
11558
|
+
var toPosix3 = (p) => p.split(sep6).join("/");
|
|
11559
|
+
function provenanceManifests(collections, configDir, base) {
|
|
11560
|
+
const out = [];
|
|
11561
|
+
for (const collection of collections) {
|
|
11562
|
+
for (const manifest of collection.externalMetadata) {
|
|
11563
|
+
if (!manifest.keys.includes(PROVENANCE_FIELD)) continue;
|
|
11564
|
+
if (classifyRef(manifest.file).kind === "url") {
|
|
11565
|
+
throw new DocmetaError(
|
|
11566
|
+
`collection ${collection.name}: ${PROVENANCE_FIELD} cannot come from a URL manifest, because manni meta derive writes it.`
|
|
11567
|
+
);
|
|
11568
|
+
}
|
|
11569
|
+
const absPath = isAbsolute6(manifest.file) ? manifest.file : resolve12(configDir, manifest.file);
|
|
11570
|
+
const rel = relative11(base, absPath);
|
|
11571
|
+
out.push({
|
|
11572
|
+
collection: collection.name,
|
|
11573
|
+
absPath,
|
|
11574
|
+
file: rel === "" ? "." : toPosix3(rel),
|
|
11575
|
+
join: externalMetadataJoin(manifest)
|
|
11576
|
+
});
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
return out;
|
|
11580
|
+
}
|
|
11581
|
+
function provenancePlace(label, data, manifests, collections, configDir, base) {
|
|
11582
|
+
if (manifests.length === 0) return void 0;
|
|
11583
|
+
const members = new Set(memberOf(collections, configDir, base, label));
|
|
11584
|
+
const owning = manifests.filter((m) => members.has(m.collection));
|
|
11585
|
+
const [manifest, second] = owning;
|
|
11586
|
+
if (manifest === void 0) return void 0;
|
|
11587
|
+
if (second !== void 0) {
|
|
11588
|
+
throw new DocmetaError(
|
|
11589
|
+
`${label} is in collections ${manifest.collection} and ${second.collection}, and both keep ${PROVENANCE_FIELD} in a manifest.`
|
|
11590
|
+
);
|
|
11591
|
+
}
|
|
11592
|
+
if (manifest.join === PATH_JOIN) {
|
|
11593
|
+
const entry = toPosix3(relative11(configDir, resolve12(base, label)));
|
|
11594
|
+
return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
|
|
11595
|
+
}
|
|
11596
|
+
const value = data[manifest.join];
|
|
11597
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
11598
|
+
throw new DocmetaError(
|
|
11599
|
+
`${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${PROVENANCE_FIELD} has no entry there.`
|
|
11600
|
+
);
|
|
11601
|
+
}
|
|
11602
|
+
return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
|
|
11603
|
+
}
|
|
11604
|
+
function readerManifests(collections, configDir, base) {
|
|
11605
|
+
const local = collections.map((c) => ({
|
|
11606
|
+
...c,
|
|
11607
|
+
externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
|
|
11608
|
+
}));
|
|
11609
|
+
return provenanceManifests(local, configDir, base);
|
|
11610
|
+
}
|
|
11611
|
+
function readerPlace(label, data, manifests, collections, configDir, base) {
|
|
11612
|
+
try {
|
|
11613
|
+
const place = provenancePlace(label, data, manifests, collections, configDir, base);
|
|
11614
|
+
return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
|
|
11615
|
+
} catch (err) {
|
|
11616
|
+
if (err instanceof DocmetaError) return void 0;
|
|
11617
|
+
throw err;
|
|
11618
|
+
}
|
|
11619
|
+
}
|
|
11620
|
+
|
|
10716
11621
|
// src/meta/commands/validate.ts
|
|
10717
11622
|
function parseErrorResult(file, format, message, keyword) {
|
|
10718
11623
|
const err = {
|
|
@@ -10836,6 +11741,7 @@ async function runValidate(opts) {
|
|
|
10836
11741
|
const deriveFields = new Set(deriveWillRun ? deriveConfig.fields : []);
|
|
10837
11742
|
const readable = derivableFields(deriveCommands);
|
|
10838
11743
|
for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
|
|
11744
|
+
const provenanceManifests2 = keepDeriveInputs && deriveFields.has(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
|
|
10839
11745
|
const derivedOnce = once(async () => {
|
|
10840
11746
|
const derived = await deriveMetadata(deriveInputs, {
|
|
10841
11747
|
cwd,
|
|
@@ -10846,7 +11752,8 @@ async function runValidate(opts) {
|
|
|
10846
11752
|
...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
|
|
10847
11753
|
...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
|
|
10848
11754
|
cache: opts.cache ?? true,
|
|
10849
|
-
now: opts.now ?? (() => /* @__PURE__ */ new Date())
|
|
11755
|
+
now: opts.now ?? (() => /* @__PURE__ */ new Date()),
|
|
11756
|
+
machines: machinesOf(deriveConfig)
|
|
10850
11757
|
});
|
|
10851
11758
|
assertSourcesAvailable(
|
|
10852
11759
|
derived.sources,
|
|
@@ -10880,7 +11787,14 @@ async function runValidate(opts) {
|
|
|
10880
11787
|
return;
|
|
10881
11788
|
}
|
|
10882
11789
|
if (keepDeriveInputs && label !== STDIN_LABEL) {
|
|
10883
|
-
|
|
11790
|
+
const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
|
|
11791
|
+
deriveInputs.push({
|
|
11792
|
+
label,
|
|
11793
|
+
absPath: resolve13(base, label),
|
|
11794
|
+
content,
|
|
11795
|
+
extracted,
|
|
11796
|
+
...place !== void 0 ? { provenanceManifest: place } : {}
|
|
11797
|
+
});
|
|
10884
11798
|
}
|
|
10885
11799
|
const merged = mergeExternalMetadata(label, extracted, externalMetadata, members, base, {
|
|
10886
11800
|
encryptionKey
|
|
@@ -10993,7 +11907,7 @@ async function runValidate(opts) {
|
|
|
10993
11907
|
await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
|
|
10994
11908
|
}
|
|
10995
11909
|
for (const file of files) {
|
|
10996
|
-
const content = await readFile8(
|
|
11910
|
+
const content = await readFile8(resolve13(base, file), "utf8");
|
|
10997
11911
|
await processOne(file, content, extname3(file));
|
|
10998
11912
|
}
|
|
10999
11913
|
if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
|
|
@@ -11083,18 +11997,23 @@ async function runValidate(opts) {
|
|
|
11083
11997
|
}
|
|
11084
11998
|
marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
|
|
11085
11999
|
}
|
|
11086
|
-
const
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
12000
|
+
const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
|
|
12001
|
+
const findings = fields.flatMap((field) => {
|
|
12002
|
+
if (field === PROVENANCE_FIELD) {
|
|
12003
|
+
const derivation = derived.provenance?.get(input.label);
|
|
12004
|
+
if (derivation === void 0) return [];
|
|
12005
|
+
return provenanceFindings(
|
|
12006
|
+
compareProvenance(provenanceEntries(asserted(field)), derivation),
|
|
12007
|
+
derivation.page.bodyLine
|
|
11092
12008
|
);
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
12009
|
+
}
|
|
12010
|
+
const compared = compareDerived(field, asserted(field), record.fields[field]);
|
|
12011
|
+
const at2 = pointerOf(field);
|
|
12012
|
+
return staleFindings(
|
|
12013
|
+
[marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
|
|
12014
|
+
input.extracted.lineFor
|
|
12015
|
+
);
|
|
12016
|
+
});
|
|
11098
12017
|
if (findings.length === 0) continue;
|
|
11099
12018
|
result.errors.push(...findings);
|
|
11100
12019
|
result.ok = false;
|
|
@@ -11132,13 +12051,13 @@ async function runValidate(opts) {
|
|
|
11132
12051
|
|
|
11133
12052
|
// src/meta/commands/get.ts
|
|
11134
12053
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
11135
|
-
import { resolve as
|
|
12054
|
+
import { resolve as resolve14, extname as extname4 } from "node:path";
|
|
11136
12055
|
async function runGet(opts) {
|
|
11137
12056
|
const cwd = opts.cwd ?? process.cwd();
|
|
11138
12057
|
if (opts.fields.length === 0) {
|
|
11139
12058
|
throw new DocmetaError("Specify at least one field to get.");
|
|
11140
12059
|
}
|
|
11141
|
-
const { config, inputs, base, configDir, collections, fromCollections, configFile } = await resolveRunConfig({
|
|
12060
|
+
const { config, inputs, base, configDir, collections, declaredCollections, fromCollections, configFile } = await resolveRunConfig({
|
|
11142
12061
|
cwd,
|
|
11143
12062
|
configPath: opts.configPath,
|
|
11144
12063
|
noConfig: opts.noConfig,
|
|
@@ -11197,7 +12116,10 @@ async function runGet(opts) {
|
|
|
11197
12116
|
const deriving = opts.derived !== false;
|
|
11198
12117
|
const derivableFields2 = deriving ? opts.fields.filter((f) => isBuiltinField(f) || Object.hasOwn(commands, f)) : [];
|
|
11199
12118
|
const deriveInputs = [];
|
|
12119
|
+
const wantsProvenance = opts.fields.includes(PROVENANCE_FIELD);
|
|
12120
|
+
const provenanceManifests2 = deriving && derivableFields2.includes(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
|
|
11200
12121
|
const carried = /* @__PURE__ */ new Map();
|
|
12122
|
+
const bodyLines = /* @__PURE__ */ new Map();
|
|
11201
12123
|
const readOne = (label, content, extension) => {
|
|
11202
12124
|
const extractor = forced ?? extractorForExtension(extension);
|
|
11203
12125
|
if (!extractor) {
|
|
@@ -11212,13 +12134,18 @@ async function runGet(opts) {
|
|
|
11212
12134
|
elements: resolveElements(label, config, members)
|
|
11213
12135
|
});
|
|
11214
12136
|
if (deriving && label !== STDIN_LABEL) {
|
|
12137
|
+
const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, own2.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
|
|
11215
12138
|
deriveInputs.push({
|
|
11216
12139
|
label,
|
|
11217
|
-
absPath:
|
|
12140
|
+
absPath: resolve14(base, label),
|
|
11218
12141
|
content,
|
|
11219
|
-
extracted: own2
|
|
12142
|
+
extracted: own2,
|
|
12143
|
+
...place !== void 0 ? { provenanceManifest: place } : {}
|
|
11220
12144
|
});
|
|
11221
12145
|
}
|
|
12146
|
+
if (wantsProvenance) {
|
|
12147
|
+
bodyLines.set(label, readProvenancePage(content, { fenced: provenanceFenced(own2) }).bodyLine);
|
|
12148
|
+
}
|
|
11222
12149
|
extracted = mergeExternalMetadata(label, own2, externalMetadata, members, base, {
|
|
11223
12150
|
encryptionKey: joinKey
|
|
11224
12151
|
}).extracted;
|
|
@@ -11235,7 +12162,13 @@ async function runGet(opts) {
|
|
|
11235
12162
|
carries[f] = carriesField(extracted.data, f);
|
|
11236
12163
|
}
|
|
11237
12164
|
carried.set(label, carries);
|
|
11238
|
-
|
|
12165
|
+
const bodyLine = bodyLines.get(label);
|
|
12166
|
+
out.push({
|
|
12167
|
+
file: label,
|
|
12168
|
+
present: extracted.present,
|
|
12169
|
+
values,
|
|
12170
|
+
...bodyLine !== void 0 ? { provenance: { bodyLine } } : {}
|
|
12171
|
+
});
|
|
11239
12172
|
};
|
|
11240
12173
|
if (usingStdin) {
|
|
11241
12174
|
if (!forced) {
|
|
@@ -11246,7 +12179,7 @@ async function runGet(opts) {
|
|
|
11246
12179
|
readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
11247
12180
|
}
|
|
11248
12181
|
for (const file of files) {
|
|
11249
|
-
const content = await readFile9(
|
|
12182
|
+
const content = await readFile9(resolve14(base, file), "utf8");
|
|
11250
12183
|
readOne(file, content, extname4(file));
|
|
11251
12184
|
}
|
|
11252
12185
|
if (deriving) {
|
|
@@ -11269,6 +12202,7 @@ async function runGet(opts) {
|
|
|
11269
12202
|
async function attachResolved(out, inputs, opts) {
|
|
11270
12203
|
const { run: run2 } = opts;
|
|
11271
12204
|
const derive = run2.config?.derive;
|
|
12205
|
+
let provenance = /* @__PURE__ */ new Map();
|
|
11272
12206
|
const commands = commandsOf(derive);
|
|
11273
12207
|
const records = opts.derivable.length === 0 || inputs.length === 0 ? /* @__PURE__ */ new Map() : await (async () => {
|
|
11274
12208
|
const result = await deriveMetadata(inputs, {
|
|
@@ -11280,12 +12214,14 @@ async function attachResolved(out, inputs, opts) {
|
|
|
11280
12214
|
...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
|
|
11281
12215
|
...commands !== void 0 ? { commands } : {},
|
|
11282
12216
|
cache: run2.cache,
|
|
11283
|
-
now: run2.now
|
|
12217
|
+
now: run2.now,
|
|
12218
|
+
machines: machinesOf(derive)
|
|
11284
12219
|
});
|
|
11285
12220
|
assertSourcesAvailable(
|
|
11286
12221
|
result.sources,
|
|
11287
12222
|
"pass --no-derived, or narrow derive.sources"
|
|
11288
12223
|
);
|
|
12224
|
+
if (result.provenance !== void 0) provenance = result.provenance;
|
|
11289
12225
|
return result.records;
|
|
11290
12226
|
})();
|
|
11291
12227
|
for (const r of out) {
|
|
@@ -11313,6 +12249,11 @@ async function attachResolved(out, inputs, opts) {
|
|
|
11313
12249
|
if (opts.derivable.length > 0) r.derived = derived;
|
|
11314
12250
|
r.resolved = resolved;
|
|
11315
12251
|
r.origin = origin;
|
|
12252
|
+
const derivation = provenance.get(r.file);
|
|
12253
|
+
if (r.provenance !== void 0 && derivation !== void 0) {
|
|
12254
|
+
const comparisons = compareProvenance(provenanceEntries(r.values[PROVENANCE_FIELD]), derivation);
|
|
12255
|
+
r.provenance.current = comparisons.every((c) => c.status === "current");
|
|
12256
|
+
}
|
|
11316
12257
|
}
|
|
11317
12258
|
}
|
|
11318
12259
|
function resolveField(data, field) {
|
|
@@ -11365,15 +12306,15 @@ function parseJsonPointer(pointer) {
|
|
|
11365
12306
|
// src/meta/commands/schemas.ts
|
|
11366
12307
|
import { existsSync as existsSync4 } from "node:fs";
|
|
11367
12308
|
import { mkdir as mkdir2, readFile as readFile10 } from "node:fs/promises";
|
|
11368
|
-
import { dirname as dirname10, extname as extname5, isAbsolute as
|
|
11369
|
-
import { parseDocument as parseDocument5, parse as
|
|
12309
|
+
import { dirname as dirname10, extname as extname5, isAbsolute as isAbsolute7, join as join8, relative as relative12, resolve as resolve15, sep as sep7 } from "node:path";
|
|
12310
|
+
import { parseDocument as parseDocument5, parse as parseYaml5, stringify as stringify2 } from "yaml";
|
|
11370
12311
|
function getSchemasInfo() {
|
|
11371
12312
|
return { builtins: listBuiltins(), formats: listFormats() };
|
|
11372
12313
|
}
|
|
11373
12314
|
var DEFAULT_VENDOR_DIR = "./schema";
|
|
11374
12315
|
var DEFAULT_CONFIG_NAME = "manni.config.yaml";
|
|
11375
12316
|
function posixRelative(from, to) {
|
|
11376
|
-
return
|
|
12317
|
+
return relative12(from, to).split(sep7).join("/");
|
|
11377
12318
|
}
|
|
11378
12319
|
function vendorFileName(url) {
|
|
11379
12320
|
let parsed2;
|
|
@@ -11447,7 +12388,7 @@ function reparseConfig(text2, source, section) {
|
|
|
11447
12388
|
parseConfig(text2, source);
|
|
11448
12389
|
return;
|
|
11449
12390
|
}
|
|
11450
|
-
const doc =
|
|
12391
|
+
const doc = parseYaml5(text2);
|
|
11451
12392
|
parseConfigValue(doc[section], source, section);
|
|
11452
12393
|
}
|
|
11453
12394
|
async function runVendorSchema(opts) {
|
|
@@ -11459,13 +12400,13 @@ async function runVendorSchema(opts) {
|
|
|
11459
12400
|
);
|
|
11460
12401
|
}
|
|
11461
12402
|
const loaded = await loadConfig(opts.configPath, cwd);
|
|
11462
|
-
const configPath = loaded?.path ??
|
|
12403
|
+
const configPath = loaded?.path ?? resolve15(cwd, DEFAULT_CONFIG_NAME);
|
|
11463
12404
|
const configDir = loaded?.dir ?? cwd;
|
|
11464
|
-
const absDir =
|
|
12405
|
+
const absDir = resolve15(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
|
|
11465
12406
|
const absFile = join8(absDir, vendorFileName(url));
|
|
11466
12407
|
await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
|
|
11467
12408
|
const fromConfig = posixRelative(configDir, absFile);
|
|
11468
|
-
const ref =
|
|
12409
|
+
const ref = isAbsolute7(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
|
|
11469
12410
|
const { bytes } = await fetchSchemaBytes(url, {
|
|
11470
12411
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
11471
12412
|
...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
|
|
@@ -11642,7 +12583,7 @@ async function runInferSchema(opts) {
|
|
|
11642
12583
|
}
|
|
11643
12584
|
let absOut;
|
|
11644
12585
|
if (opts.out !== void 0) {
|
|
11645
|
-
absOut =
|
|
12586
|
+
absOut = resolve15(cwd, opts.out);
|
|
11646
12587
|
await assertNotIgnored(
|
|
11647
12588
|
absOut,
|
|
11648
12589
|
dirname10(absOut),
|
|
@@ -11730,7 +12671,7 @@ async function runInferSchema(opts) {
|
|
|
11730
12671
|
scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
11731
12672
|
}
|
|
11732
12673
|
for (const file of files) {
|
|
11733
|
-
const content = await readFile10(
|
|
12674
|
+
const content = await readFile10(resolve15(base, file), "utf8");
|
|
11734
12675
|
scanOne(file, content, extname5(file));
|
|
11735
12676
|
}
|
|
11736
12677
|
for (const u of unreadable) {
|
|
@@ -11792,7 +12733,7 @@ async function runInferSchema(opts) {
|
|
|
11792
12733
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
11793
12734
|
import { existsSync as existsSync5 } from "node:fs";
|
|
11794
12735
|
import { mkdir as mkdir3, open as open2, readFile as readFile11, rename as rename3, rm as rm3 } from "node:fs/promises";
|
|
11795
|
-
import { dirname as dirname11, isAbsolute as
|
|
12736
|
+
import { dirname as dirname11, isAbsolute as isAbsolute8, resolve as resolve16, extname as extname6, sep as sep8 } from "node:path";
|
|
11796
12737
|
|
|
11797
12738
|
// src/shared/prompt.ts
|
|
11798
12739
|
import { createInterface } from "node:readline";
|
|
@@ -12009,10 +12950,10 @@ async function runQuery(opts) {
|
|
|
12009
12950
|
readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
|
|
12010
12951
|
}
|
|
12011
12952
|
for (const file of files) {
|
|
12012
|
-
const content = await readFile11(
|
|
12953
|
+
const content = await readFile11(resolve16(base, file), "utf8");
|
|
12013
12954
|
readOne(file, content, extname6(file));
|
|
12014
12955
|
}
|
|
12015
|
-
const db = opts.db === void 0 ? void 0 : { resolved:
|
|
12956
|
+
const db = opts.db === void 0 ? void 0 : { resolved: resolve16(cwd, opts.db), display: opts.db };
|
|
12016
12957
|
const run2 = await runSql(sql, entries, {
|
|
12017
12958
|
target: db,
|
|
12018
12959
|
params: boundParams,
|
|
@@ -12143,13 +13084,21 @@ async function runSql(sql, entries, ctx) {
|
|
|
12143
13084
|
if (target) buildViews();
|
|
12144
13085
|
const buildDerived = async () => {
|
|
12145
13086
|
if (derivedTable.built) return;
|
|
12146
|
-
const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => ({
|
|
12147
|
-
label: e.label,
|
|
12148
|
-
absPath: resolve15(ctx.base, e.label),
|
|
12149
|
-
content: e.content,
|
|
12150
|
-
extracted: e.own
|
|
12151
|
-
}));
|
|
12152
13087
|
const commands = commandsOf(ctx.config?.derive);
|
|
13088
|
+
const fields = fieldsForSql(sql, derivableFields(commands));
|
|
13089
|
+
const provenanceDerived = fields.includes(PROVENANCE_FIELD) || ctx.config?.derive?.fields.includes(PROVENANCE_FIELD) === true;
|
|
13090
|
+
const manifestRoot = ctx.configDir ?? ctx.cwd;
|
|
13091
|
+
const manifests = provenanceDerived ? readerManifests(ctx.declaredCollections, manifestRoot, ctx.base) : [];
|
|
13092
|
+
const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => {
|
|
13093
|
+
const place = manifests.length === 0 ? void 0 : readerPlace(e.label, e.own.data, manifests, ctx.declaredCollections, manifestRoot, ctx.base);
|
|
13094
|
+
return {
|
|
13095
|
+
label: e.label,
|
|
13096
|
+
absPath: resolve16(ctx.base, e.label),
|
|
13097
|
+
content: e.content,
|
|
13098
|
+
extracted: e.own,
|
|
13099
|
+
...place !== void 0 ? { provenanceManifest: place } : {}
|
|
13100
|
+
};
|
|
13101
|
+
});
|
|
12153
13102
|
const records = await deriveForTable(
|
|
12154
13103
|
inputs,
|
|
12155
13104
|
{
|
|
@@ -12161,7 +13110,7 @@ async function runSql(sql, entries, ctx) {
|
|
|
12161
13110
|
...ctx.now !== void 0 ? { now: ctx.now } : {}
|
|
12162
13111
|
},
|
|
12163
13112
|
"narrow derive.sources in manni.config.yaml",
|
|
12164
|
-
|
|
13113
|
+
fields
|
|
12165
13114
|
);
|
|
12166
13115
|
createDerivedView(db, records, derivedColumns(commands));
|
|
12167
13116
|
createResolvedView(db, dataColumns, commands);
|
|
@@ -12603,20 +13552,20 @@ function displayPath(ctx, abs) {
|
|
|
12603
13552
|
}
|
|
12604
13553
|
function assertSchemaWriteWithin(ctx, abs) {
|
|
12605
13554
|
const within = posixRelative(ctx.trustRoot.dir, abs);
|
|
12606
|
-
if (within !== "" && !within.startsWith("..") && !
|
|
13555
|
+
if (within !== "" && !within.startsWith("..") && !isAbsolute8(within)) return;
|
|
12607
13556
|
throw new DocmetaError(
|
|
12608
13557
|
`The resolved schema "${abs}" lives outside ${ctx.trustRoot.dir}; DDL edits schemas inside the repository. Evolve it where it lives, or vendor a copy in.`
|
|
12609
13558
|
);
|
|
12610
13559
|
}
|
|
12611
13560
|
function foldPath(p) {
|
|
12612
|
-
const abs =
|
|
13561
|
+
const abs = resolve16(p);
|
|
12613
13562
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
12614
13563
|
}
|
|
12615
13564
|
function filePinsByPath(ctx) {
|
|
12616
13565
|
const byPath = /* @__PURE__ */ new Map();
|
|
12617
13566
|
for (const [ref, pin] of collectSchemaPins(ctx.config)) {
|
|
12618
13567
|
if (classifyRef(ref).kind !== "file") continue;
|
|
12619
|
-
byPath.set(foldPath(
|
|
13568
|
+
byPath.set(foldPath(resolve16(ctx.cwd, ref)), pin);
|
|
12620
13569
|
}
|
|
12621
13570
|
return byPath;
|
|
12622
13571
|
}
|
|
@@ -12644,7 +13593,7 @@ async function loadSetMembers(refs, ctx) {
|
|
|
12644
13593
|
});
|
|
12645
13594
|
continue;
|
|
12646
13595
|
}
|
|
12647
|
-
const abs =
|
|
13596
|
+
const abs = resolve16(ctx.cwd, ref);
|
|
12648
13597
|
const canon = foldPath(abs);
|
|
12649
13598
|
if (seenFiles.has(canon)) continue;
|
|
12650
13599
|
seenFiles.add(canon);
|
|
@@ -12849,7 +13798,7 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
12849
13798
|
const [, name2, ver] = target.builtinId.split(":");
|
|
12850
13799
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
12851
13800
|
const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
|
|
12852
|
-
schemaAbs =
|
|
13801
|
+
schemaAbs = resolve16(ctx.configDir ?? ctx.cwd, "schemas", forkName);
|
|
12853
13802
|
assertSchemaWriteWithin(ctx, schemaAbs);
|
|
12854
13803
|
if (existsSync5(schemaAbs)) {
|
|
12855
13804
|
throw new DocmetaError(
|
|
@@ -13016,7 +13965,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
13016
13965
|
}
|
|
13017
13966
|
return void 0;
|
|
13018
13967
|
};
|
|
13019
|
-
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !
|
|
13968
|
+
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute8(raw) ? resolve16(configDir, raw) : raw;
|
|
13020
13969
|
const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
|
|
13021
13970
|
const seqResolvesToRunSet = (node) => {
|
|
13022
13971
|
if (!isSeq3(node)) return false;
|
|
@@ -13061,7 +14010,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
13061
14010
|
if (!isMap5(item)) continue;
|
|
13062
14011
|
const raw = rawRefOf(item);
|
|
13063
14012
|
if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
|
|
13064
|
-
const abs =
|
|
14013
|
+
const abs = isAbsolute8(raw) ? raw : resolve16(configDir, raw);
|
|
13065
14014
|
if (foldPath(abs) !== wantPath) continue;
|
|
13066
14015
|
pinFrom = item.get("integrity");
|
|
13067
14016
|
item.set("integrity", edit.integrity.value);
|
|
@@ -13282,7 +14231,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
13282
14231
|
`"${file}": no writable format for that extension.`
|
|
13283
14232
|
);
|
|
13284
14233
|
}
|
|
13285
|
-
if (existsSync5(
|
|
14234
|
+
if (existsSync5(resolve16(ctx.base, file))) {
|
|
13286
14235
|
throw new DocmetaError(
|
|
13287
14236
|
`"${file}" already exists; INSERT creates new files only.`
|
|
13288
14237
|
);
|
|
@@ -13301,7 +14250,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
13301
14250
|
`"${from}" -> "${to}": a rename may not change the extension.`
|
|
13302
14251
|
);
|
|
13303
14252
|
}
|
|
13304
|
-
if (existsSync5(
|
|
14253
|
+
if (existsSync5(resolve16(ctx.base, to))) {
|
|
13305
14254
|
throw new DocmetaError(
|
|
13306
14255
|
`"${to}" already exists; the rename would overwrite it.`
|
|
13307
14256
|
);
|
|
@@ -13330,13 +14279,13 @@ function validateNewPath(p, base) {
|
|
|
13330
14279
|
const refuse = (why) => {
|
|
13331
14280
|
throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
|
|
13332
14281
|
};
|
|
13333
|
-
if (
|
|
14282
|
+
if (isAbsolute8(p)) refuse("it is absolute");
|
|
13334
14283
|
if (p.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
13335
14284
|
refuse("it traverses upward");
|
|
13336
14285
|
}
|
|
13337
|
-
const abs =
|
|
13338
|
-
if (abs ===
|
|
13339
|
-
if (!abs.startsWith(
|
|
14286
|
+
const abs = resolve16(base, p);
|
|
14287
|
+
if (abs === resolve16(base)) refuse("it points at the corpus root, not a file");
|
|
14288
|
+
if (!abs.startsWith(resolve16(base) + sep8)) refuse("it escapes the corpus");
|
|
13340
14289
|
}
|
|
13341
14290
|
function refuseExternalMetadataWrites(changes, entries, ctx) {
|
|
13342
14291
|
const index = ctx.externalMetadata;
|
|
@@ -13365,7 +14314,7 @@ function refuseExternalMetadataWrites(changes, entries, ctx) {
|
|
|
13365
14314
|
continue;
|
|
13366
14315
|
}
|
|
13367
14316
|
if ("renamed" in c) {
|
|
13368
|
-
const entry = index.byPath.get(
|
|
14317
|
+
const entry = index.byPath.get(resolve16(ctx.base, c.file));
|
|
13369
14318
|
if (entry !== void 0 && entry.size > 0) {
|
|
13370
14319
|
const manifest = [...entry.values()][0]?.file ?? "manifest";
|
|
13371
14320
|
throw new DocmetaError(
|
|
@@ -13400,8 +14349,8 @@ function readableJoinValue(held, ctx) {
|
|
|
13400
14349
|
return opened.ok ? opened.value : held;
|
|
13401
14350
|
}
|
|
13402
14351
|
var sealedWrites = /* @__PURE__ */ new WeakMap();
|
|
13403
|
-
function writtenValue(
|
|
13404
|
-
const sealed = sealedWrites.get(
|
|
14352
|
+
function writtenValue(change, key, value) {
|
|
14353
|
+
const sealed = sealedWrites.get(change)?.get(key);
|
|
13405
14354
|
return sealed === void 0 ? value : sealed;
|
|
13406
14355
|
}
|
|
13407
14356
|
async function sealMarkedWrites(changes, entries, ctx) {
|
|
@@ -13503,14 +14452,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
13503
14452
|
const pendingWrites = [];
|
|
13504
14453
|
const pendingRenames = [];
|
|
13505
14454
|
for (const [label, ops] of grouped) {
|
|
13506
|
-
const path =
|
|
14455
|
+
const path = resolve16(ctx.base, label);
|
|
13507
14456
|
if (ops.renamedTo !== void 0) {
|
|
13508
14457
|
if (!existsSync5(path)) {
|
|
13509
14458
|
throw new DocmetaError(
|
|
13510
14459
|
`"${label}" is gone from disk since it was read; re-run the query.`
|
|
13511
14460
|
);
|
|
13512
14461
|
}
|
|
13513
|
-
const toDisk =
|
|
14462
|
+
const toDisk = resolve16(ctx.base, ops.renamedTo);
|
|
13514
14463
|
if (existsSync5(toDisk)) {
|
|
13515
14464
|
throw new DocmetaError(
|
|
13516
14465
|
`"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
|
|
@@ -13754,7 +14703,7 @@ function skipExpression(sql, from) {
|
|
|
13754
14703
|
|
|
13755
14704
|
// src/meta/commands/fill.ts
|
|
13756
14705
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
13757
|
-
import { resolve as
|
|
14706
|
+
import { resolve as resolve17, extname as extname7, join as join9 } from "node:path";
|
|
13758
14707
|
import {
|
|
13759
14708
|
completeValidatedJSON,
|
|
13760
14709
|
makeProvider,
|
|
@@ -13769,7 +14718,7 @@ import {
|
|
|
13769
14718
|
} from "@hawkeyexl/inference";
|
|
13770
14719
|
|
|
13771
14720
|
// src/meta/commands/fill-prompt.ts
|
|
13772
|
-
var FILL_PROMPT_VERSION =
|
|
14721
|
+
var FILL_PROMPT_VERSION = 4;
|
|
13773
14722
|
var DEFAULT_CHUNK_CHARS = 12e3;
|
|
13774
14723
|
function splitBody(body, chunkChars) {
|
|
13775
14724
|
if (body.length <= chunkChars) return [body];
|
|
@@ -13877,6 +14826,8 @@ function buildUserPrompt(params) {
|
|
|
13877
14826
|
}
|
|
13878
14827
|
|
|
13879
14828
|
// src/meta/commands/fill.ts
|
|
14829
|
+
var META_PROVENANCE_KEY = "meta-provenance";
|
|
14830
|
+
var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["provenance", META_PROVENANCE_KEY]);
|
|
13880
14831
|
var DEFAULT_THRESHOLD = 0.7;
|
|
13881
14832
|
var DEFAULT_CONCURRENCY = 4;
|
|
13882
14833
|
var DEFAULT_PROVIDER = "auto";
|
|
@@ -14157,7 +15108,7 @@ async function runFill(opts) {
|
|
|
14157
15108
|
only
|
|
14158
15109
|
);
|
|
14159
15110
|
const managedSkips = proposed.filter((c) => managed.has(c.key)).map((c) => ({
|
|
14160
|
-
field:
|
|
15111
|
+
field: pointerOf(c.key),
|
|
14161
15112
|
required: c.required,
|
|
14162
15113
|
confidence: 0,
|
|
14163
15114
|
reasoning: "managed by derive",
|
|
@@ -14181,6 +15132,7 @@ async function runFill(opts) {
|
|
|
14181
15132
|
const inPlace = view.plain.flatMap((pointer) => {
|
|
14182
15133
|
const key = topLevelKeyOf(pointer);
|
|
14183
15134
|
if (key === void 0 || only !== void 0 && !only.has(key)) return [];
|
|
15135
|
+
if (PROVENANCE_KEYS.has(key)) return [];
|
|
14184
15136
|
if (externalMetadata?.owners.has(key) === true) return [];
|
|
14185
15137
|
if (candidates.some((c) => c.key === key)) return [];
|
|
14186
15138
|
return [{ key, pointer, value: extracted.data[key] }];
|
|
@@ -14224,7 +15176,11 @@ async function runFill(opts) {
|
|
|
14224
15176
|
sha256(content)
|
|
14225
15177
|
]);
|
|
14226
15178
|
const hit = candidates.length > 0 ? cache?.get(cacheKey) : void 0;
|
|
14227
|
-
const existing =
|
|
15179
|
+
const existing = Object.fromEntries(
|
|
15180
|
+
Object.entries(redactForModel(extracted.data, [...view.marked, ...probeMarks])).filter(
|
|
15181
|
+
([key]) => !PROVENANCE_KEYS.has(key)
|
|
15182
|
+
)
|
|
15183
|
+
);
|
|
14228
15184
|
let proposals = {};
|
|
14229
15185
|
if (hit) {
|
|
14230
15186
|
cachedCount++;
|
|
@@ -14377,7 +15333,8 @@ async function runFill(opts) {
|
|
|
14377
15333
|
...opts.includeContent ? { content } : {}
|
|
14378
15334
|
};
|
|
14379
15335
|
}
|
|
14380
|
-
const
|
|
15336
|
+
const plainPatch = patchOf(writable);
|
|
15337
|
+
const finalMarks = writable.length > 0 ? await validator.markedPointers({ ...view.data, ...plainPatch }, schemaSet) : /* @__PURE__ */ new Set();
|
|
14381
15338
|
const patch = {};
|
|
14382
15339
|
for (const f of writable) {
|
|
14383
15340
|
const key = keyOf(f);
|
|
@@ -14403,6 +15360,23 @@ async function runFill(opts) {
|
|
|
14403
15360
|
encrypted: true
|
|
14404
15361
|
});
|
|
14405
15362
|
}
|
|
15363
|
+
let metaProvenance;
|
|
15364
|
+
if (writable.length > 0) {
|
|
15365
|
+
const owner = externalMetadata?.owners.get(META_PROVENANCE_KEY)?.find((o) => members.includes(o.collection));
|
|
15366
|
+
const merged2 = owner === void 0 ? mergeMetaProvenance(extracted.data[META_PROVENANCE_KEY], identity.model, writable) : void 0;
|
|
15367
|
+
if (owner !== void 0) {
|
|
15368
|
+
metaProvenance = { written: false, skipReason: "manifest-owned", manifest: owner.file };
|
|
15369
|
+
} else if (merged2 === void 0 || await addsFindings(
|
|
15370
|
+
(data) => validator.validate(data, schemaSet, extracted.lineFor, extracted.colFor).then((errors) => settleFindings(errors, view, extracted)),
|
|
15371
|
+
{ ...view.data, ...plainPatch },
|
|
15372
|
+
merged2.list
|
|
15373
|
+
)) {
|
|
15374
|
+
metaProvenance = { written: false, skipReason: "schema-mismatch" };
|
|
15375
|
+
} else {
|
|
15376
|
+
patch[META_PROVENANCE_KEY] = merged2.list;
|
|
15377
|
+
metaProvenance = { written: true, entry: merged2.entry };
|
|
15378
|
+
}
|
|
15379
|
+
}
|
|
14406
15380
|
let next;
|
|
14407
15381
|
try {
|
|
14408
15382
|
next = extractor.apply(content, patch, {
|
|
@@ -14410,17 +15384,25 @@ async function runFill(opts) {
|
|
|
14410
15384
|
elements
|
|
14411
15385
|
});
|
|
14412
15386
|
} catch (err) {
|
|
14413
|
-
|
|
14414
|
-
label,
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
15387
|
+
const withoutEntry = metaProvenance?.written === true ? tryApply(extractor, content, withoutKey(patch, META_PROVENANCE_KEY), {
|
|
15388
|
+
filePath: label,
|
|
15389
|
+
elements
|
|
15390
|
+
}) : void 0;
|
|
15391
|
+
if (withoutEntry === void 0) {
|
|
15392
|
+
return errorResult(
|
|
15393
|
+
label,
|
|
15394
|
+
extractor.name,
|
|
15395
|
+
errorMessage(err),
|
|
15396
|
+
schemaSet,
|
|
15397
|
+
fields
|
|
15398
|
+
);
|
|
15399
|
+
}
|
|
15400
|
+
next = withoutEntry;
|
|
15401
|
+
metaProvenance = { written: false, skipReason: "unwritable" };
|
|
14420
15402
|
}
|
|
14421
15403
|
const changed = next !== content;
|
|
14422
15404
|
if (changed && !dryRun && label !== "<stdin>") {
|
|
14423
|
-
await writeFileAtomic(
|
|
15405
|
+
await writeFileAtomic(resolve17(base, label), next);
|
|
14424
15406
|
}
|
|
14425
15407
|
return {
|
|
14426
15408
|
file: label,
|
|
@@ -14428,7 +15410,8 @@ async function runFill(opts) {
|
|
|
14428
15410
|
schemas: schemaSet,
|
|
14429
15411
|
fields,
|
|
14430
15412
|
changed,
|
|
14431
|
-
...opts.includeContent ? { content: next } : {}
|
|
15413
|
+
...opts.includeContent ? { content: next } : {},
|
|
15414
|
+
...metaProvenance === void 0 ? {} : { metaProvenance }
|
|
14432
15415
|
};
|
|
14433
15416
|
};
|
|
14434
15417
|
const results = [];
|
|
@@ -14442,7 +15425,7 @@ async function runFill(opts) {
|
|
|
14442
15425
|
);
|
|
14443
15426
|
}
|
|
14444
15427
|
const fileResults = await mapConcurrent(files, concurrency, async (file) => {
|
|
14445
|
-
const content = await readFile12(
|
|
15428
|
+
const content = await readFile12(resolve17(base, file), "utf8");
|
|
14446
15429
|
return processOne(file, content, extname7(file));
|
|
14447
15430
|
});
|
|
14448
15431
|
results.push(...fileResults);
|
|
@@ -14461,7 +15444,57 @@ function topKey(instancePath) {
|
|
|
14461
15444
|
const seg = instancePath.slice(1).split("/")[0] ?? "";
|
|
14462
15445
|
return seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
14463
15446
|
}
|
|
14464
|
-
var keyOf = (f) => f.field
|
|
15447
|
+
var keyOf = (f) => topKey(f.field);
|
|
15448
|
+
function isPlainRecord(value) {
|
|
15449
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15450
|
+
}
|
|
15451
|
+
function mergeMetaProvenance(held, model, written) {
|
|
15452
|
+
if (held !== void 0 && !Array.isArray(held)) return void 0;
|
|
15453
|
+
const list = held === void 0 ? [] : [...held];
|
|
15454
|
+
const at2 = list.findIndex((e) => isPlainRecord(e) && e["generated-by"] === model);
|
|
15455
|
+
const prior = list[at2];
|
|
15456
|
+
const kept = isPlainRecord(prior) ? prior : {};
|
|
15457
|
+
const fields = Array.isArray(kept.fields) ? kept.fields.filter((p) => typeof p === "string") : [];
|
|
15458
|
+
const confidence = {};
|
|
15459
|
+
if (isPlainRecord(kept.confidence)) {
|
|
15460
|
+
for (const [k, v] of Object.entries(kept.confidence)) {
|
|
15461
|
+
if (typeof v === "number") confidence[k] = v;
|
|
15462
|
+
}
|
|
15463
|
+
}
|
|
15464
|
+
for (const f of written) {
|
|
15465
|
+
if (!fields.includes(f.field)) fields.push(f.field);
|
|
15466
|
+
confidence[f.field] = f.confidence;
|
|
15467
|
+
}
|
|
15468
|
+
const entry = { ...kept, "generated-by": model, fields, confidence };
|
|
15469
|
+
if (at2 === -1) list.push(entry);
|
|
15470
|
+
else list[at2] = entry;
|
|
15471
|
+
return { list, entry };
|
|
15472
|
+
}
|
|
15473
|
+
async function addsFindings(validate, without, list) {
|
|
15474
|
+
const identity = (e) => JSON.stringify([e.schema, e.instancePath, e.keyword, e.subject ?? null, e.message]);
|
|
15475
|
+
const before = /* @__PURE__ */ new Map();
|
|
15476
|
+
for (const e of await validate(without)) {
|
|
15477
|
+
const key = identity(e);
|
|
15478
|
+
before.set(key, (before.get(key) ?? 0) + 1);
|
|
15479
|
+
}
|
|
15480
|
+
for (const e of await validate({ ...without, [META_PROVENANCE_KEY]: list })) {
|
|
15481
|
+
const key = identity(e);
|
|
15482
|
+
const left = before.get(key) ?? 0;
|
|
15483
|
+
if (left === 0) return true;
|
|
15484
|
+
before.set(key, left - 1);
|
|
15485
|
+
}
|
|
15486
|
+
return false;
|
|
15487
|
+
}
|
|
15488
|
+
function withoutKey(patch, key) {
|
|
15489
|
+
return Object.fromEntries(Object.entries(patch).filter(([k]) => k !== key));
|
|
15490
|
+
}
|
|
15491
|
+
function tryApply(extractor, content, patch, options) {
|
|
15492
|
+
try {
|
|
15493
|
+
return extractor.apply?.(content, patch, options);
|
|
15494
|
+
} catch {
|
|
15495
|
+
return void 0;
|
|
15496
|
+
}
|
|
15497
|
+
}
|
|
14465
15498
|
var IN_PLACE_REASONING = "The page held this value in plain text; its schema marks it x-manni-encrypt, so it was encrypted.";
|
|
14466
15499
|
var CIPHERTEXT_IN_TEXT = /~[A-Za-z0-9_-]{82,}/g;
|
|
14467
15500
|
function contentForModel(content, extractor, existing, held, supplied, options) {
|
|
@@ -14470,10 +15503,13 @@ function contentForModel(content, extractor, existing, held, supplied, options)
|
|
|
14470
15503
|
if (!Object.hasOwn(held, key) || supplied(pointerOf(key))) continue;
|
|
14471
15504
|
if (toJsonText(value) !== toJsonText(held[key])) patch[key] = value;
|
|
14472
15505
|
}
|
|
15506
|
+
const deletions = [...PROVENANCE_KEYS].filter(
|
|
15507
|
+
(key) => Object.hasOwn(held, key) && !supplied(pointerOf(key))
|
|
15508
|
+
);
|
|
14473
15509
|
let text2 = content;
|
|
14474
|
-
if (Object.keys(patch).length > 0 && extractor.apply !== void 0) {
|
|
15510
|
+
if ((Object.keys(patch).length > 0 || deletions.length > 0) && extractor.apply !== void 0) {
|
|
14475
15511
|
try {
|
|
14476
|
-
text2 = extractor.apply(content, patch, options);
|
|
15512
|
+
text2 = extractor.apply(content, patch, { ...options, deletions });
|
|
14477
15513
|
} catch {
|
|
14478
15514
|
}
|
|
14479
15515
|
}
|
|
@@ -14511,7 +15547,7 @@ function collectCandidates(schemas, data, errors, only) {
|
|
|
14511
15547
|
for (const [key, raw] of Object.entries(
|
|
14512
15548
|
properties
|
|
14513
15549
|
)) {
|
|
14514
|
-
if (key === FILE_SCHEMA_KEY) continue;
|
|
15550
|
+
if (key === FILE_SCHEMA_KEY || PROVENANCE_KEYS.has(key)) continue;
|
|
14515
15551
|
if (only && !only.has(key)) continue;
|
|
14516
15552
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) continue;
|
|
14517
15553
|
const present = Object.prototype.hasOwnProperty.call(data, key);
|
|
@@ -14677,7 +15713,7 @@ function gate(candidates, proposals, threshold) {
|
|
|
14677
15713
|
const proposal = proposals[c.key];
|
|
14678
15714
|
if (proposal == null) {
|
|
14679
15715
|
return {
|
|
14680
|
-
field:
|
|
15716
|
+
field: pointerOf(c.key),
|
|
14681
15717
|
required: c.required,
|
|
14682
15718
|
confidence: 0,
|
|
14683
15719
|
reasoning: "",
|
|
@@ -14687,7 +15723,7 @@ function gate(candidates, proposals, threshold) {
|
|
|
14687
15723
|
}
|
|
14688
15724
|
const passes = proposal.confidence >= threshold;
|
|
14689
15725
|
return {
|
|
14690
|
-
field:
|
|
15726
|
+
field: pointerOf(c.key),
|
|
14691
15727
|
required: c.required,
|
|
14692
15728
|
confidence: proposal.confidence,
|
|
14693
15729
|
reasoning: proposal.reasoning,
|
|
@@ -14786,10 +15822,434 @@ async function mapConcurrent(items2, limit, fn) {
|
|
|
14786
15822
|
|
|
14787
15823
|
// src/meta/commands/derive.ts
|
|
14788
15824
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
14789
|
-
import { extname as extname8, resolve as
|
|
15825
|
+
import { extname as extname8, relative as relative13, resolve as resolve18, sep as sep9 } from "node:path";
|
|
15826
|
+
|
|
15827
|
+
// src/meta/core/external-metadata-write.ts
|
|
15828
|
+
import { posix as posix3 } from "node:path";
|
|
15829
|
+
import {
|
|
15830
|
+
LineCounter as LineCounter3,
|
|
15831
|
+
isMap as isMap6,
|
|
15832
|
+
isNode as isNode2,
|
|
15833
|
+
isPair,
|
|
15834
|
+
isScalar as isScalar4,
|
|
15835
|
+
isSeq as isSeq4,
|
|
15836
|
+
parseDocument as parseDocument7,
|
|
15837
|
+
stringify as stringify3
|
|
15838
|
+
} from "yaml";
|
|
15839
|
+
function spliceManifestValue(text2, options) {
|
|
15840
|
+
const { entry, key, value, file } = options;
|
|
15841
|
+
const join10 = options.join ?? PATH_JOIN;
|
|
15842
|
+
const at2 = (line2) => {
|
|
15843
|
+
const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
15844
|
+
if (line2 === void 0) return name2;
|
|
15845
|
+
return file === void 0 ? `${name2} line ${String(line2)}` : `${name2}:${String(line2)}`;
|
|
15846
|
+
};
|
|
15847
|
+
if (key === FILE_SCHEMA_KEY) {
|
|
15848
|
+
throw new DocmetaError(
|
|
15849
|
+
`${at2()}: refusing to write "${FILE_SCHEMA_KEY}" for "${entry}"; a manifest never chooses the schema a document is judged by. Use "overrides" in the config.`
|
|
15850
|
+
);
|
|
15851
|
+
}
|
|
15852
|
+
if (value === void 0) {
|
|
15853
|
+
throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
|
|
15854
|
+
}
|
|
15855
|
+
const eol = detectEol(text2);
|
|
15856
|
+
const lc = new LineCounter3();
|
|
15857
|
+
const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
|
|
15858
|
+
const problem = doc.errors[0];
|
|
15859
|
+
if (problem) {
|
|
15860
|
+
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
15861
|
+
}
|
|
15862
|
+
const keyLine = (node) => {
|
|
15863
|
+
const r = rangeOf(node);
|
|
15864
|
+
return r ? lc.linePos(r[0]).line : void 0;
|
|
15865
|
+
};
|
|
15866
|
+
const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
15867
|
+
const root = doc.contents;
|
|
15868
|
+
let spelled = entry;
|
|
15869
|
+
let edit;
|
|
15870
|
+
if (root === null || isScalar4(root) && root.value === null && emptySource(text2, root)) {
|
|
15871
|
+
edit = startManifest(text2, entry, key, value, eol);
|
|
15872
|
+
} else if (!isMap6(root)) {
|
|
15873
|
+
throw new DocmetaError(
|
|
15874
|
+
`${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
|
|
15875
|
+
);
|
|
15876
|
+
} else {
|
|
15877
|
+
const style = detectStyle(text2, root, eol);
|
|
15878
|
+
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
15879
|
+
const [first, second] = matches;
|
|
15880
|
+
if (first && second) {
|
|
15881
|
+
throw new DocmetaError(
|
|
15882
|
+
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
15883
|
+
);
|
|
15884
|
+
}
|
|
15885
|
+
if (first === void 0) {
|
|
15886
|
+
if (root.flow === true && root.items.length > 0) {
|
|
15887
|
+
throw new DocmetaError(
|
|
15888
|
+
`${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
|
|
15889
|
+
);
|
|
15890
|
+
}
|
|
15891
|
+
edit = appendEntry(text2, root, entry, key, value, style);
|
|
15892
|
+
} else {
|
|
15893
|
+
spelled = keyString(first.key);
|
|
15894
|
+
const where = at2(keyLine(first.key));
|
|
15895
|
+
const v = first.value;
|
|
15896
|
+
if (!isMap6(v)) {
|
|
15897
|
+
throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
|
|
15898
|
+
}
|
|
15899
|
+
const hits = v.items.filter((kv) => keyString(kv.key) === key);
|
|
15900
|
+
const [hit, again] = hits;
|
|
15901
|
+
if (hit && again) {
|
|
15902
|
+
throw new DocmetaError(
|
|
15903
|
+
`${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
|
|
15904
|
+
);
|
|
15905
|
+
}
|
|
15906
|
+
const flowRefusal = () => new DocmetaError(
|
|
15907
|
+
`${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not add keys to. Rewrite the entry in block style.`
|
|
15908
|
+
);
|
|
15909
|
+
const formRefusal = () => new DocmetaError(
|
|
15910
|
+
`${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
|
|
15911
|
+
);
|
|
15912
|
+
let made;
|
|
15913
|
+
if (v.flow === true) {
|
|
15914
|
+
if (hit) made = replaceFlowValue(text2, hit, value);
|
|
15915
|
+
else if (v.items.length === 0) made = fillEmptyEntry(text2, first, key, value, style);
|
|
15916
|
+
else throw flowRefusal();
|
|
15917
|
+
if (!made) throw hit ? flowRefusal() : formRefusal();
|
|
15918
|
+
} else {
|
|
15919
|
+
made = hit ? replaceBlockValue(text2, hit, value, style) : insertKey(text2, v, key, value, style);
|
|
15920
|
+
if (!made) throw formRefusal();
|
|
15921
|
+
}
|
|
15922
|
+
edit = made;
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
const out = text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
|
|
15926
|
+
const line = readBack(
|
|
15927
|
+
doc,
|
|
15928
|
+
out,
|
|
15929
|
+
spelled,
|
|
15930
|
+
key,
|
|
15931
|
+
value,
|
|
15932
|
+
(why) => new DocmetaError(
|
|
15933
|
+
`${at2()}: writing "${key}" for "${entry}" did not read back as written, so nothing was written. ${why}`
|
|
15934
|
+
)
|
|
15935
|
+
);
|
|
15936
|
+
return { text: out, line };
|
|
15937
|
+
}
|
|
15938
|
+
function removeManifestKey(text2, options) {
|
|
15939
|
+
const { entry, key, file } = options;
|
|
15940
|
+
const join10 = options.join ?? PATH_JOIN;
|
|
15941
|
+
const at2 = (line) => {
|
|
15942
|
+
const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
|
|
15943
|
+
if (line === void 0) return name2;
|
|
15944
|
+
return file === void 0 ? `${name2} line ${String(line)}` : `${name2}:${String(line)}`;
|
|
15945
|
+
};
|
|
15946
|
+
const eol = detectEol(text2);
|
|
15947
|
+
const lc = new LineCounter3();
|
|
15948
|
+
const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
|
|
15949
|
+
const problem = doc.errors[0];
|
|
15950
|
+
if (problem) {
|
|
15951
|
+
throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
|
|
15952
|
+
}
|
|
15953
|
+
const keyLine = (node) => {
|
|
15954
|
+
const r = rangeOf(node);
|
|
15955
|
+
return r ? lc.linePos(r[0]).line : void 0;
|
|
15956
|
+
};
|
|
15957
|
+
const root = doc.contents;
|
|
15958
|
+
if (root === null || isScalar4(root) && root.value === null) return { text: text2 };
|
|
15959
|
+
if (!isMap6(root)) {
|
|
15960
|
+
throw new DocmetaError(
|
|
15961
|
+
`${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
|
|
15962
|
+
);
|
|
15963
|
+
}
|
|
15964
|
+
const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
|
|
15965
|
+
const matches = root.items.filter((p) => same(keyString(p.key), entry));
|
|
15966
|
+
const [first, second] = matches;
|
|
15967
|
+
if (first && second) {
|
|
15968
|
+
throw new DocmetaError(
|
|
15969
|
+
`${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
|
|
15970
|
+
);
|
|
15971
|
+
}
|
|
15972
|
+
if (first === void 0) return { text: text2 };
|
|
15973
|
+
const spelled = keyString(first.key);
|
|
15974
|
+
const where = at2(keyLine(first.key));
|
|
15975
|
+
const v = first.value;
|
|
15976
|
+
if (!isMap6(v)) {
|
|
15977
|
+
throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
|
|
15978
|
+
}
|
|
15979
|
+
const hits = v.items.filter((kv) => keyString(kv.key) === key);
|
|
15980
|
+
const [hit, again] = hits;
|
|
15981
|
+
if (hit && again) {
|
|
15982
|
+
throw new DocmetaError(
|
|
15983
|
+
`${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
|
|
15984
|
+
);
|
|
15985
|
+
}
|
|
15986
|
+
if (hit === void 0) return { text: text2 };
|
|
15987
|
+
const whole = v.items.length === 1;
|
|
15988
|
+
if (!whole && v.flow === true) {
|
|
15989
|
+
throw new DocmetaError(
|
|
15990
|
+
`${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not remove keys from. Rewrite the entry in block style.`
|
|
15991
|
+
);
|
|
15992
|
+
}
|
|
15993
|
+
const from = rangeOf(whole ? first.key : hit.key);
|
|
15994
|
+
const to = whole ? contentEnd(text2, v) : contentEnd(text2, hit.value) ?? contentEnd(text2, hit.key);
|
|
15995
|
+
if (!from || to === void 0) {
|
|
15996
|
+
throw new DocmetaError(
|
|
15997
|
+
`${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
|
|
15998
|
+
);
|
|
15999
|
+
}
|
|
16000
|
+
let start = text2.lastIndexOf("\n", from[0] - 1) + 1;
|
|
16001
|
+
let end = lineEnd(text2, to);
|
|
16002
|
+
if (text2.startsWith(eol, end)) end += eol.length;
|
|
16003
|
+
else if (start >= eol.length) start -= eol.length;
|
|
16004
|
+
if (whole) {
|
|
16005
|
+
if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
|
|
16006
|
+
else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
|
|
16007
|
+
}
|
|
16008
|
+
const out = text2.slice(0, start) + text2.slice(end);
|
|
16009
|
+
const after = parseDocument7(out, { uniqueKeys: false });
|
|
16010
|
+
const fail2 = (why) => new DocmetaError(
|
|
16011
|
+
`${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
|
|
16012
|
+
);
|
|
16013
|
+
if (after.errors[0]) throw fail2(`The result is not valid YAML: ${after.errors[0].message}`);
|
|
16014
|
+
const was = doc.toJS({ maxAliasCount: 100 });
|
|
16015
|
+
const wasRoot = isRecord5(was) ? was : {};
|
|
16016
|
+
const wasEntry = wasRoot[spelled];
|
|
16017
|
+
const rest = Object.fromEntries(
|
|
16018
|
+
Object.entries(isRecord5(wasEntry) ? wasEntry : {}).filter(([k]) => k !== key)
|
|
16019
|
+
);
|
|
16020
|
+
const kept = Object.entries(wasRoot).filter(([k]) => k !== spelled);
|
|
16021
|
+
if (Object.keys(rest).length > 0) kept.push([spelled, rest]);
|
|
16022
|
+
const now = after.toJS({ maxAliasCount: 100 });
|
|
16023
|
+
const expected = kept.length === 0 ? null : Object.fromEntries(kept);
|
|
16024
|
+
if (!deepEqual(now ?? null, expected)) throw fail2("Another value in the manifest would have changed.");
|
|
16025
|
+
return { text: out };
|
|
16026
|
+
}
|
|
16027
|
+
function startManifest(text2, entry, key, value, eol) {
|
|
16028
|
+
const style = { step: 2, indentSeq: true, eol };
|
|
16029
|
+
const block = blockLines({ [entry]: { [key]: value } }, 0, style);
|
|
16030
|
+
let insert;
|
|
16031
|
+
if (text2 === "" || text2.endsWith("\n")) insert = block + eol;
|
|
16032
|
+
else insert = eol + block;
|
|
16033
|
+
return { start: text2.length, end: text2.length, insert };
|
|
16034
|
+
}
|
|
16035
|
+
function appendEntry(text2, root, entry, key, value, style) {
|
|
16036
|
+
const body = { [entry]: { [key]: value } };
|
|
16037
|
+
if (root.flow === true) {
|
|
16038
|
+
const r = rangeOf(root);
|
|
16039
|
+
const start = r ? r[0] : text2.length;
|
|
16040
|
+
const end = contentEnd(text2, root) ?? start;
|
|
16041
|
+
return { start, end, insert: blockLines(body, colOf(text2, start), style) };
|
|
16042
|
+
}
|
|
16043
|
+
const firstKey = rangeOf(root.items[0]?.key);
|
|
16044
|
+
const col = firstKey ? colOf(text2, firstKey[0]) : 0;
|
|
16045
|
+
const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
|
|
16046
|
+
const last = root.items.at(-1);
|
|
16047
|
+
const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
|
|
16048
|
+
return {
|
|
16049
|
+
start: pos,
|
|
16050
|
+
end: pos,
|
|
16051
|
+
insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
|
|
16052
|
+
};
|
|
16053
|
+
}
|
|
16054
|
+
function insertKey(text2, entryMap, key, value, style) {
|
|
16055
|
+
const firstKey = rangeOf(entryMap.items[0]?.key);
|
|
16056
|
+
const end = contentEnd(text2, entryMap);
|
|
16057
|
+
if (!firstKey || end === void 0) return void 0;
|
|
16058
|
+
const pos = lineEnd(text2, end);
|
|
16059
|
+
return {
|
|
16060
|
+
start: pos,
|
|
16061
|
+
end: pos,
|
|
16062
|
+
insert: style.eol + blockLines({ [key]: value }, colOf(text2, firstKey[0]), style)
|
|
16063
|
+
};
|
|
16064
|
+
}
|
|
16065
|
+
function fillEmptyEntry(text2, entryPair, key, value, style) {
|
|
16066
|
+
const start = colonEnd(text2, entryPair);
|
|
16067
|
+
const keyRange = rangeOf(entryPair.key);
|
|
16068
|
+
const end = contentEnd(text2, entryPair.value);
|
|
16069
|
+
if (start === void 0 || !keyRange || end === void 0) return void 0;
|
|
16070
|
+
const col = colOf(text2, keyRange[0]) + style.step;
|
|
16071
|
+
return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
|
|
16072
|
+
}
|
|
16073
|
+
function replaceBlockValue(text2, pair, value, style) {
|
|
16074
|
+
const colon = colonEnd(text2, pair);
|
|
16075
|
+
const keyRange = rangeOf(pair.key);
|
|
16076
|
+
if (colon === void 0 || !keyRange) return void 0;
|
|
16077
|
+
const keyCol = colOf(text2, keyRange[0]);
|
|
16078
|
+
let start = colon;
|
|
16079
|
+
let ownLine = false;
|
|
16080
|
+
let i = colon;
|
|
16081
|
+
while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
|
|
16082
|
+
if (text2.charAt(i) === "#") {
|
|
16083
|
+
start = lineEnd(text2, i);
|
|
16084
|
+
ownLine = true;
|
|
16085
|
+
}
|
|
16086
|
+
const old = pair.value;
|
|
16087
|
+
const oldEnd = isScalar4(old) && old.value === null && emptySource(text2, old) ? start : contentEnd(text2, old) ?? start;
|
|
16088
|
+
const end = Math.max(start, oldEnd);
|
|
16089
|
+
let insert = afterColon(value, keyCol, style);
|
|
16090
|
+
if (ownLine && !insert.startsWith(style.eol)) {
|
|
16091
|
+
insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
|
|
16092
|
+
}
|
|
16093
|
+
return { start, end, insert };
|
|
16094
|
+
}
|
|
16095
|
+
function replaceFlowValue(text2, pair, value) {
|
|
16096
|
+
const r = rangeOf(pair.value);
|
|
16097
|
+
if (!r || emptySource(text2, pair.value)) return void 0;
|
|
16098
|
+
const end = contentEnd(text2, pair.value) ?? r[1];
|
|
16099
|
+
return { start: r[0], end, insert: flowText(value) };
|
|
16100
|
+
}
|
|
16101
|
+
function readBack(before, out, spelled, key, value, fail2) {
|
|
16102
|
+
const lc = new LineCounter3();
|
|
16103
|
+
const after = parseDocument7(out, { lineCounter: lc, uniqueKeys: false });
|
|
16104
|
+
const problem = after.errors[0];
|
|
16105
|
+
if (problem) throw fail2(`The result is not valid YAML: ${problem.message}`);
|
|
16106
|
+
const was = before.toJS({ maxAliasCount: 100 });
|
|
16107
|
+
const now = after.toJS({ maxAliasCount: 100 });
|
|
16108
|
+
const wasRoot = isRecord5(was) ? was : {};
|
|
16109
|
+
const wasEntry = wasRoot[spelled];
|
|
16110
|
+
const expected = {
|
|
16111
|
+
...wasRoot,
|
|
16112
|
+
[spelled]: { ...isRecord5(wasEntry) ? wasEntry : {}, [key]: value }
|
|
16113
|
+
};
|
|
16114
|
+
const nowEntry = isRecord5(now) ? now[spelled] : void 0;
|
|
16115
|
+
if (!isRecord5(nowEntry) || !deepEqual(nowEntry[key], value)) {
|
|
16116
|
+
throw fail2(
|
|
16117
|
+
"The value reads back differently. A Date, an undefined member, or another value YAML cannot hold does not survive the trip."
|
|
16118
|
+
);
|
|
16119
|
+
}
|
|
16120
|
+
if (!deepEqual(now, expected)) {
|
|
16121
|
+
throw fail2("Another value in the manifest would have changed.");
|
|
16122
|
+
}
|
|
16123
|
+
const root = after.contents;
|
|
16124
|
+
const pair = isMap6(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
|
|
16125
|
+
const entryMap = pair?.value;
|
|
16126
|
+
const kv = isMap6(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
|
|
16127
|
+
const r = rangeOf(kv?.value) ?? rangeOf(kv?.key);
|
|
16128
|
+
if (!r) throw fail2("The written value could not be found again.");
|
|
16129
|
+
return lc.linePos(r[0]).line;
|
|
16130
|
+
}
|
|
16131
|
+
function blockLines(pair, col, style) {
|
|
16132
|
+
const s = stringify3(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
|
|
16133
|
+
return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
|
|
16134
|
+
}
|
|
16135
|
+
function afterColon(value, col, style) {
|
|
16136
|
+
const s = stringify3({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
|
|
16137
|
+
const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
|
|
16138
|
+
return indentLines(lines, col, 1).join(style.eol);
|
|
16139
|
+
}
|
|
16140
|
+
function indentLines(lines, col, from) {
|
|
16141
|
+
const pad2 = " ".repeat(col);
|
|
16142
|
+
return lines.map((l, i) => i < from || l === "" ? l : pad2 + l);
|
|
16143
|
+
}
|
|
16144
|
+
function flowText(value) {
|
|
16145
|
+
const s = stringify3(value, { lineWidth: 0, collectionStyle: "flow" }).replace(/\n$/, "");
|
|
16146
|
+
return s.includes("\n") ? JSON.stringify(value) : s;
|
|
16147
|
+
}
|
|
16148
|
+
function detectEol(text2) {
|
|
16149
|
+
const i = text2.indexOf("\n");
|
|
16150
|
+
return i > 0 && text2.charAt(i - 1) === "\r" ? "\r\n" : "\n";
|
|
16151
|
+
}
|
|
16152
|
+
function detectStyle(text2, root, eol) {
|
|
16153
|
+
let step = 2;
|
|
16154
|
+
for (const pair of root.items) {
|
|
16155
|
+
const v = pair.value;
|
|
16156
|
+
const entryKey = rangeOf(pair.key);
|
|
16157
|
+
const ownedKey = isMap6(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
|
|
16158
|
+
if (entryKey && ownedKey) {
|
|
16159
|
+
const d = colOf(text2, ownedKey[0]) - colOf(text2, entryKey[0]);
|
|
16160
|
+
if (d > 0) {
|
|
16161
|
+
step = d;
|
|
16162
|
+
break;
|
|
16163
|
+
}
|
|
16164
|
+
}
|
|
16165
|
+
}
|
|
16166
|
+
return { step, indentSeq: seqIndented(text2, root) ?? true, eol };
|
|
16167
|
+
}
|
|
16168
|
+
function seqIndented(text2, node) {
|
|
16169
|
+
if (isMap6(node)) {
|
|
16170
|
+
for (const pair of node.items) {
|
|
16171
|
+
const v = pair.value;
|
|
16172
|
+
if (isSeq4(v) && v.flow !== true && v.items.length > 0) {
|
|
16173
|
+
const k = rangeOf(pair.key);
|
|
16174
|
+
const s = rangeOf(v);
|
|
16175
|
+
if (k && s) return colOf(text2, s[0]) > colOf(text2, k[0]);
|
|
16176
|
+
}
|
|
16177
|
+
const inner = seqIndented(text2, v);
|
|
16178
|
+
if (inner !== void 0) return inner;
|
|
16179
|
+
}
|
|
16180
|
+
} else if (isSeq4(node)) {
|
|
16181
|
+
for (const item of node.items) {
|
|
16182
|
+
const inner = seqIndented(text2, item);
|
|
16183
|
+
if (inner !== void 0) return inner;
|
|
16184
|
+
}
|
|
16185
|
+
}
|
|
16186
|
+
return void 0;
|
|
16187
|
+
}
|
|
16188
|
+
function contentEnd(text2, node) {
|
|
16189
|
+
if (isMap6(node) && node.flow !== true) {
|
|
16190
|
+
const last = node.items.at(-1);
|
|
16191
|
+
if (last) return contentEnd(text2, last.value) ?? contentEnd(text2, last.key);
|
|
16192
|
+
} else if (isSeq4(node) && node.flow !== true) {
|
|
16193
|
+
const last = node.items.at(-1);
|
|
16194
|
+
if (last !== void 0) {
|
|
16195
|
+
return isPair(last) ? contentEnd(text2, last.value) ?? contentEnd(text2, last.key) : contentEnd(text2, last);
|
|
16196
|
+
}
|
|
16197
|
+
}
|
|
16198
|
+
const r = rangeOf(node);
|
|
16199
|
+
if (!r) return void 0;
|
|
16200
|
+
let end = r[1];
|
|
16201
|
+
while (end > r[0] && /\s/.test(text2.charAt(end - 1))) end--;
|
|
16202
|
+
return end;
|
|
16203
|
+
}
|
|
16204
|
+
function colonEnd(text2, pair) {
|
|
16205
|
+
const r = rangeOf(pair.key);
|
|
16206
|
+
if (!r) return void 0;
|
|
16207
|
+
let i = r[1];
|
|
16208
|
+
while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
|
|
16209
|
+
return text2.charAt(i) === ":" ? i + 1 : void 0;
|
|
16210
|
+
}
|
|
16211
|
+
function lineEnd(text2, i) {
|
|
16212
|
+
const j = text2.indexOf("\n", i);
|
|
16213
|
+
if (j < 0) return text2.length;
|
|
16214
|
+
return j > 0 && text2.charAt(j - 1) === "\r" ? j - 1 : j;
|
|
16215
|
+
}
|
|
16216
|
+
function colOf(text2, offset) {
|
|
16217
|
+
if (offset === 0) return 0;
|
|
16218
|
+
let start = text2.lastIndexOf("\n", offset - 1) + 1;
|
|
16219
|
+
if (start === 0 && text2.startsWith("\uFEFF")) start = 1;
|
|
16220
|
+
return offset - start;
|
|
16221
|
+
}
|
|
16222
|
+
function rangeOf(node) {
|
|
16223
|
+
return isNode2(node) ? node.range ?? void 0 : void 0;
|
|
16224
|
+
}
|
|
16225
|
+
function emptySource(text2, node) {
|
|
16226
|
+
const r = rangeOf(node);
|
|
16227
|
+
return !r || text2.slice(r[0], r[1]).trim() === "";
|
|
16228
|
+
}
|
|
16229
|
+
function keyString(key) {
|
|
16230
|
+
return isScalar4(key) ? String(key.value) : String(key);
|
|
16231
|
+
}
|
|
16232
|
+
function isRecord5(x) {
|
|
16233
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
16234
|
+
}
|
|
16235
|
+
|
|
16236
|
+
// src/meta/commands/derive.ts
|
|
16237
|
+
var GENERATED_BY_ENV = "MANNI_GENERATED_BY";
|
|
14790
16238
|
var SOURCE_HINT = "narrow --sources or --fields";
|
|
14791
16239
|
async function runDerive(opts) {
|
|
14792
16240
|
const cwd = opts.cwd ?? process.cwd();
|
|
16241
|
+
const targets = [];
|
|
16242
|
+
const typedInputs = opts.inputs.map((arg) => {
|
|
16243
|
+
if (arg === STDIN_TOKEN) return arg;
|
|
16244
|
+
const { page, lines } = parseProvenanceTarget(arg);
|
|
16245
|
+
if (lines === void 0) return arg;
|
|
16246
|
+
if (page === STDIN_TOKEN) return STDIN_TOKEN;
|
|
16247
|
+
targets.push({ target: arg, page });
|
|
16248
|
+
return page;
|
|
16249
|
+
});
|
|
16250
|
+
const env = opts.env ?? process.env;
|
|
16251
|
+
const flagGeneratedBy = machineName(opts.generatedBy);
|
|
16252
|
+
const generatedBy = opts.generatedBy !== void 0 ? flagGeneratedBy : machineName(env[GENERATED_BY_ENV]);
|
|
14793
16253
|
const {
|
|
14794
16254
|
config,
|
|
14795
16255
|
inputs,
|
|
@@ -14803,7 +16263,7 @@ async function runDerive(opts) {
|
|
|
14803
16263
|
cwd,
|
|
14804
16264
|
configPath: opts.configPath,
|
|
14805
16265
|
noConfig: opts.noConfig,
|
|
14806
|
-
inputs:
|
|
16266
|
+
inputs: typedInputs,
|
|
14807
16267
|
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
14808
16268
|
onConfigLoaded: opts.onConfigLoaded
|
|
14809
16269
|
});
|
|
@@ -14817,7 +16277,20 @@ async function runDerive(opts) {
|
|
|
14817
16277
|
}
|
|
14818
16278
|
const commands = commandsOf(config?.derive);
|
|
14819
16279
|
const fields = resolveFields(opts.fields, config?.derive?.fields, commands);
|
|
16280
|
+
const wantsProvenance = fields.includes(PROVENANCE_FIELD);
|
|
16281
|
+
const firstTarget = targets[0];
|
|
16282
|
+
if (firstTarget !== void 0 && generatedBy === void 0) {
|
|
16283
|
+
throw new DocmetaError(
|
|
16284
|
+
`${firstTarget.target} names lines, which only --generated-by uses. Pass --generated-by, or drop the range.`
|
|
16285
|
+
);
|
|
16286
|
+
}
|
|
16287
|
+
if ((flagGeneratedBy !== void 0 || firstTarget !== void 0) && !wantsProvenance) {
|
|
16288
|
+
throw new DocmetaError(
|
|
16289
|
+
"--generated-by attributes provenance, which is not in --fields. Add provenance, or drop --generated-by."
|
|
16290
|
+
);
|
|
16291
|
+
}
|
|
14820
16292
|
for (const field of fields) {
|
|
16293
|
+
if (field === PROVENANCE_FIELD) continue;
|
|
14821
16294
|
const owner = manifestOwning(field, declaredCollections);
|
|
14822
16295
|
if (owner !== void 0) {
|
|
14823
16296
|
throw new DocmetaError(
|
|
@@ -14880,6 +16353,18 @@ async function runDerive(opts) {
|
|
|
14880
16353
|
})
|
|
14881
16354
|
});
|
|
14882
16355
|
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
16356
|
+
const rangesByPage = /* @__PURE__ */ new Map();
|
|
16357
|
+
for (const t of targets) {
|
|
16358
|
+
const label = relative13(base, resolve18(base, t.page)).split(sep9).join("/");
|
|
16359
|
+
if (!files.includes(label)) {
|
|
16360
|
+
throw new DocmetaError(
|
|
16361
|
+
`${t.target} does not name one file that derive reads; a range names lines of one file.`
|
|
16362
|
+
);
|
|
16363
|
+
}
|
|
16364
|
+
const named = rangesByPage.get(label) ?? [];
|
|
16365
|
+
if (!named.includes(t.target)) named.push(t.target);
|
|
16366
|
+
rangesByPage.set(label, named);
|
|
16367
|
+
}
|
|
14883
16368
|
assertNonEmpty({
|
|
14884
16369
|
files,
|
|
14885
16370
|
inputs,
|
|
@@ -14893,7 +16378,7 @@ async function runDerive(opts) {
|
|
|
14893
16378
|
const loaded = /* @__PURE__ */ new Map();
|
|
14894
16379
|
const errors = /* @__PURE__ */ new Map();
|
|
14895
16380
|
for (const label of files) {
|
|
14896
|
-
const absPath =
|
|
16381
|
+
const absPath = resolve18(base, label);
|
|
14897
16382
|
const extractor = forcedExtractor ?? extractorForExtension(extname8(label));
|
|
14898
16383
|
if (!extractor) {
|
|
14899
16384
|
errors.set(label, {
|
|
@@ -14918,6 +16403,38 @@ async function runDerive(opts) {
|
|
|
14918
16403
|
errors.set(label, { format: extractor.name, message: errorMessage(err) });
|
|
14919
16404
|
}
|
|
14920
16405
|
}
|
|
16406
|
+
const provenanceRoot = configDir ?? cwd;
|
|
16407
|
+
const manifests = wantsProvenance ? provenanceManifests(declaredCollections, provenanceRoot, base) : [];
|
|
16408
|
+
const provenanceCollections = declaredCollections.map((c) => ({
|
|
16409
|
+
...c,
|
|
16410
|
+
externalMetadata: c.externalMetadata.filter((m) => m.keys.includes(PROVENANCE_FIELD))
|
|
16411
|
+
})).filter((c) => c.externalMetadata.length > 0);
|
|
16412
|
+
const provenanceIndex = manifests.length === 0 ? null : await loadExternalMetadata(provenanceCollections, { configDir: provenanceRoot, base });
|
|
16413
|
+
const places = /* @__PURE__ */ new Map();
|
|
16414
|
+
for (const doc of loaded.values()) {
|
|
16415
|
+
if (!wantsProvenance) break;
|
|
16416
|
+
const place = provenancePlace(
|
|
16417
|
+
doc.label,
|
|
16418
|
+
doc.extracted.data,
|
|
16419
|
+
manifests,
|
|
16420
|
+
declaredCollections,
|
|
16421
|
+
provenanceRoot,
|
|
16422
|
+
base
|
|
16423
|
+
);
|
|
16424
|
+
if (place !== void 0) {
|
|
16425
|
+
places.set(doc.label, place);
|
|
16426
|
+
doc.provenanceManifest = { absPath: place.absPath, entry: place.entry, join: place.join };
|
|
16427
|
+
}
|
|
16428
|
+
const named = rangesByPage.get(doc.label);
|
|
16429
|
+
if (named !== void 0 && generatedBy !== void 0) {
|
|
16430
|
+
doc.attribution = { targets: named, generatedBy };
|
|
16431
|
+
}
|
|
16432
|
+
}
|
|
16433
|
+
const stampOf = (doc) => {
|
|
16434
|
+
if (!places.has(doc.label)) return doc.extracted.data[PROVENANCE_FIELD];
|
|
16435
|
+
const members = memberOf(provenanceCollections, provenanceRoot, base, doc.label);
|
|
16436
|
+
return mergeExternalMetadata(doc.label, doc.extracted, provenanceIndex, members, base).extracted.data[PROVENANCE_FIELD];
|
|
16437
|
+
};
|
|
14921
16438
|
const derived = await deriveMetadata([...loaded.values()], {
|
|
14922
16439
|
cwd,
|
|
14923
16440
|
base,
|
|
@@ -14928,7 +16445,9 @@ async function runDerive(opts) {
|
|
|
14928
16445
|
commands,
|
|
14929
16446
|
cache: opts.cache ?? true,
|
|
14930
16447
|
now: opts.now ?? (() => /* @__PURE__ */ new Date()),
|
|
14931
|
-
reviews: opts.reviews
|
|
16448
|
+
reviews: opts.reviews,
|
|
16449
|
+
machines: machinesOf(config?.derive),
|
|
16450
|
+
...generatedBy !== void 0 ? { generatedBy } : {}
|
|
14932
16451
|
});
|
|
14933
16452
|
assertSourcesAvailable(derived.sources, SOURCE_HINT);
|
|
14934
16453
|
for (const [name2, status] of Object.entries(derived.sources)) {
|
|
@@ -14997,6 +16516,20 @@ async function runDerive(opts) {
|
|
|
14997
16516
|
}
|
|
14998
16517
|
return holder[field];
|
|
14999
16518
|
};
|
|
16519
|
+
const heldManifests = /* @__PURE__ */ new Map();
|
|
16520
|
+
const holdManifest = async (manifest) => {
|
|
16521
|
+
const already = heldManifests.get(manifest.absPath);
|
|
16522
|
+
if (already !== void 0) return already;
|
|
16523
|
+
let before;
|
|
16524
|
+
try {
|
|
16525
|
+
before = await readFile13(manifest.absPath, "utf8");
|
|
16526
|
+
} catch (err) {
|
|
16527
|
+
throw new DocmetaError(`Manifest ${manifest.file} could not be read: ${errorMessage(err)}`);
|
|
16528
|
+
}
|
|
16529
|
+
const held = { path: manifest.absPath, before, text: before };
|
|
16530
|
+
heldManifests.set(manifest.absPath, held);
|
|
16531
|
+
return held;
|
|
16532
|
+
};
|
|
15000
16533
|
const results = [];
|
|
15001
16534
|
for (const label of files) {
|
|
15002
16535
|
const failure = errors.get(label);
|
|
@@ -15016,12 +16549,49 @@ async function runDerive(opts) {
|
|
|
15016
16549
|
const at2 = pointerOf(field);
|
|
15017
16550
|
return marks.marked.some((p) => isAtOrUnder(p, at2));
|
|
15018
16551
|
};
|
|
16552
|
+
const place = places.get(label);
|
|
16553
|
+
const provenance = wantsProvenance ? judgeProvenance(stampOf(doc), derived.provenance?.get(label), record?.fields[PROVENANCE_FIELD] ?? null, place) : void 0;
|
|
15019
16554
|
const compared = fields.map(
|
|
15020
|
-
(field) => compareDerived(field, marks.data[field], record?.fields[field])
|
|
16555
|
+
(field) => field === PROVENANCE_FIELD && provenance !== void 0 ? provenance.field : compareDerived(field, marks.data[field], record?.fields[field])
|
|
15021
16556
|
);
|
|
15022
|
-
const shown = () => compared.map(
|
|
15023
|
-
|
|
16557
|
+
const shown = () => compared.map(
|
|
16558
|
+
(f) => f.field !== PROVENANCE_FIELD && isMarked(f.field) ? redactedDerived(f) : f
|
|
16559
|
+
);
|
|
16560
|
+
const findingsOf = (reported2) => reported2.flatMap(
|
|
16561
|
+
(f) => f.field === PROVENANCE_FIELD && provenance?.judged !== void 0 ? provenanceFindings(provenance.judged.comparisons, provenance.judged.derivation.page.bodyLine) : staleFindings([f], doc.extracted.lineFor)
|
|
16562
|
+
);
|
|
16563
|
+
if (provenance?.judged !== void 0 && flagGeneratedBy !== void 0 && doc.attribution === void 0 && ![...provenance.judged.derivation.evidenceByLine.values()].some((e) => e.rule === 1)) {
|
|
16564
|
+
opts.onNotice?.(
|
|
16565
|
+
`${label}: no uncommitted body lines; --generated-by attributes only what is not yet committed.`
|
|
16566
|
+
);
|
|
16567
|
+
}
|
|
16568
|
+
const stale = (f) => f.status === "stale" || f.status === "unset";
|
|
16569
|
+
const toManifest = provenance !== void 0 && place !== void 0 && stale(provenance.field) ? provenance : void 0;
|
|
16570
|
+
const pending = compared.filter((f) => stale(f) && !(f.field === PROVENANCE_FIELD && toManifest !== void 0));
|
|
16571
|
+
const onPage = pending.find((f) => f.field === PROVENANCE_FIELD);
|
|
16572
|
+
if (onPage !== void 0 && !provenanceFenced(doc.extracted)) {
|
|
16573
|
+
results.push(
|
|
16574
|
+
errorResult2(
|
|
16575
|
+
label,
|
|
16576
|
+
doc.format,
|
|
16577
|
+
`provenance cannot be stamped into the page: in the "${doc.format}" format the metadata is part of the body it pins. Keep provenance in an externalMetadata manifest.`,
|
|
16578
|
+
check,
|
|
16579
|
+
shown()
|
|
16580
|
+
)
|
|
16581
|
+
);
|
|
16582
|
+
continue;
|
|
16583
|
+
}
|
|
15024
16584
|
let changed = false;
|
|
16585
|
+
if (toManifest?.judged !== void 0 && place !== void 0) {
|
|
16586
|
+
const held = await holdManifest(place.manifest);
|
|
16587
|
+
const before = held.text;
|
|
16588
|
+
const where = { entry: place.entry, key: PROVENANCE_FIELD, join: place.join, file: place.manifest.file };
|
|
16589
|
+
held.text = toManifest.judged.plan.length === 0 ? removeManifestKey(held.text, where).text : spliceManifestValue(held.text, { ...where, value: toManifest.judged.plan }).text;
|
|
16590
|
+
if (held.text !== before) {
|
|
16591
|
+
changed = true;
|
|
16592
|
+
if (!dryRun) markWritten(toManifest.field);
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
15025
16595
|
if (pending.length > 0) {
|
|
15026
16596
|
if (typeof doc.apply !== "function") {
|
|
15027
16597
|
if (!dryRun) {
|
|
@@ -15039,20 +16609,37 @@ async function runDerive(opts) {
|
|
|
15039
16609
|
changed = true;
|
|
15040
16610
|
} else {
|
|
15041
16611
|
const patch = {};
|
|
16612
|
+
const deletions = [];
|
|
15042
16613
|
for (const f of pending) {
|
|
16614
|
+
if (f.field === PROVENANCE_FIELD) {
|
|
16615
|
+
if (Array.isArray(f.derived) && f.derived.length === 0) deletions.push(f.field);
|
|
16616
|
+
else patch[f.field] = f.derived;
|
|
16617
|
+
continue;
|
|
16618
|
+
}
|
|
15043
16619
|
patch[f.field] = isMarked(f.field) ? await sealField(f.field, f.derived, marks.marked) : f.derived;
|
|
15044
16620
|
}
|
|
16621
|
+
const apply = doc.apply;
|
|
16622
|
+
const write = (value) => apply(doc.content, value, {
|
|
16623
|
+
filePath: label,
|
|
16624
|
+
elements: doc.elements,
|
|
16625
|
+
...deletions.length > 0 ? { deletions } : {}
|
|
16626
|
+
});
|
|
15045
16627
|
let next;
|
|
15046
16628
|
try {
|
|
15047
|
-
next =
|
|
16629
|
+
next = write(patch);
|
|
16630
|
+
const plan = patch[PROVENANCE_FIELD];
|
|
16631
|
+
const shift = bodyShift(doc.content, next);
|
|
16632
|
+
if (shift > 0 && Array.isArray(plan)) {
|
|
16633
|
+
next = write({ ...patch, [PROVENANCE_FIELD]: shiftEntries(provenanceEntries(plan), shift) });
|
|
16634
|
+
}
|
|
15048
16635
|
} catch (err) {
|
|
15049
16636
|
results.push(errorResult2(label, doc.format, errorMessage(err), check, shown()));
|
|
15050
16637
|
continue;
|
|
15051
16638
|
}
|
|
15052
|
-
changed = next !== doc.content;
|
|
15053
|
-
if (
|
|
16639
|
+
changed = changed || next !== doc.content;
|
|
16640
|
+
if (next !== doc.content && !dryRun) {
|
|
15054
16641
|
await writeFileAtomic(doc.absPath, next);
|
|
15055
|
-
for (const f of pending) f
|
|
16642
|
+
for (const f of pending) markWritten(f);
|
|
15056
16643
|
}
|
|
15057
16644
|
}
|
|
15058
16645
|
}
|
|
@@ -15062,18 +16649,32 @@ async function runDerive(opts) {
|
|
|
15062
16649
|
format: doc.format,
|
|
15063
16650
|
fields: reported,
|
|
15064
16651
|
changed,
|
|
15065
|
-
...check ? { findings:
|
|
16652
|
+
...check ? { findings: findingsOf(reported) } : {}
|
|
15066
16653
|
});
|
|
15067
16654
|
}
|
|
16655
|
+
if (!dryRun) {
|
|
16656
|
+
for (const held of heldManifests.values()) {
|
|
16657
|
+
if (held.text !== held.before) await writeFileAtomic(held.path, held.text);
|
|
16658
|
+
}
|
|
16659
|
+
}
|
|
16660
|
+
const summary = summarize2(results);
|
|
15068
16661
|
return {
|
|
15069
16662
|
results,
|
|
15070
|
-
summary:
|
|
16663
|
+
summary: wantsProvenance ? { ...summary, ranges: countRanges(results) } : summary,
|
|
15071
16664
|
dryRun,
|
|
15072
16665
|
check,
|
|
15073
16666
|
sources: derived.sources,
|
|
15074
16667
|
frame: { cwd, base: configDir ?? cwd, runBase: base }
|
|
15075
16668
|
};
|
|
15076
16669
|
}
|
|
16670
|
+
function deriveFailed(run2) {
|
|
16671
|
+
if (run2.summary.errors > 0) return true;
|
|
16672
|
+
return run2.check && run2.results.some((r) => (r.findings ?? []).length > 0);
|
|
16673
|
+
}
|
|
16674
|
+
function machineName(value) {
|
|
16675
|
+
const name2 = value?.trim();
|
|
16676
|
+
return name2 === void 0 || name2 === "" ? void 0 : name2;
|
|
16677
|
+
}
|
|
15077
16678
|
function resolveFields(flag, configured, commands) {
|
|
15078
16679
|
const names = flag ?? configured;
|
|
15079
16680
|
if (names === void 0 || names.length === 0) {
|
|
@@ -15141,6 +16742,59 @@ function summarize2(results) {
|
|
|
15141
16742
|
}
|
|
15142
16743
|
return summary;
|
|
15143
16744
|
}
|
|
16745
|
+
function markWritten(field) {
|
|
16746
|
+
field.written = true;
|
|
16747
|
+
for (const range of field.ranges ?? []) {
|
|
16748
|
+
if (range.status !== "current") range.written = true;
|
|
16749
|
+
}
|
|
16750
|
+
}
|
|
16751
|
+
function countRanges(results) {
|
|
16752
|
+
let n = 0;
|
|
16753
|
+
for (const r of results) {
|
|
16754
|
+
for (const f of r.fields) n += (f.ranges ?? []).filter((range) => range.written).length;
|
|
16755
|
+
}
|
|
16756
|
+
return n;
|
|
16757
|
+
}
|
|
16758
|
+
function judgeProvenance(asserted, derivation, value, place) {
|
|
16759
|
+
const base = {
|
|
16760
|
+
field: PROVENANCE_FIELD,
|
|
16761
|
+
...asserted !== void 0 ? { asserted } : {},
|
|
16762
|
+
...place !== void 0 ? { manifest: place.manifest.file } : {}
|
|
16763
|
+
};
|
|
16764
|
+
if (derivation === void 0) {
|
|
16765
|
+
return { field: { ...base, derived: null, status: "unknown", written: false } };
|
|
16766
|
+
}
|
|
16767
|
+
const comparisons = compareProvenance(provenanceEntries(asserted), derivation);
|
|
16768
|
+
const plan = planProvenanceWrite(comparisons, derivation);
|
|
16769
|
+
const settled = comparisons.every((c) => c.status === "current");
|
|
16770
|
+
const status = settled ? "current" : asserted === void 0 || comparisons.every((c) => c.status === "unset") ? "unset" : "stale";
|
|
16771
|
+
return {
|
|
16772
|
+
field: {
|
|
16773
|
+
...base,
|
|
16774
|
+
derived: plan,
|
|
16775
|
+
source: "git",
|
|
16776
|
+
...value !== null ? { evidence: value.evidence } : {},
|
|
16777
|
+
status,
|
|
16778
|
+
written: false,
|
|
16779
|
+
ranges: rangeResults(comparisons, derivation.page.bodyLine)
|
|
16780
|
+
},
|
|
16781
|
+
judged: { derivation, comparisons, plan }
|
|
16782
|
+
};
|
|
16783
|
+
}
|
|
16784
|
+
function bodyShift(before, after) {
|
|
16785
|
+
const was = readProvenancePage(before).body;
|
|
16786
|
+
const now = readProvenancePage(after).body;
|
|
16787
|
+
const shift = now.length - was.length;
|
|
16788
|
+
if (shift <= 0) return 0;
|
|
16789
|
+
return now.slice(shift).join("\n") === was.join("\n") ? shift : 0;
|
|
16790
|
+
}
|
|
16791
|
+
function shiftEntries(entries, shift) {
|
|
16792
|
+
return entries.map((e) => {
|
|
16793
|
+
const span = parseLines(e.lines);
|
|
16794
|
+
if (span === void 0) return e;
|
|
16795
|
+
return { ...e, lines: lineSpec({ start: span.start + shift, end: span.end + shift }) };
|
|
16796
|
+
});
|
|
16797
|
+
}
|
|
15144
16798
|
|
|
15145
16799
|
// src/meta/reporters/fill.ts
|
|
15146
16800
|
var FILL_FORMATS = ["pretty", "json", "github"];
|
|
@@ -15176,6 +16830,26 @@ function renderFillPretty(run2, opts = {}) {
|
|
|
15176
16830
|
` ${c.cyan(f.field)} ${formatValue(f.value)} ${c.dim(score(f.confidence))}`
|
|
15177
16831
|
);
|
|
15178
16832
|
}
|
|
16833
|
+
const record = result.metaProvenance;
|
|
16834
|
+
if (record?.written === true) {
|
|
16835
|
+
lines.push(
|
|
16836
|
+
` ${c.cyan("meta-provenance")} ${record.entry["generated-by"]}: ${record.entry.fields.join(", ")}`
|
|
16837
|
+
);
|
|
16838
|
+
} else if (record?.skipReason === "schema-mismatch") {
|
|
16839
|
+
lines.push(
|
|
16840
|
+
c.dim(" meta-provenance not written: this page's schemas do not allow it")
|
|
16841
|
+
);
|
|
16842
|
+
} else if (record?.skipReason === "manifest-owned") {
|
|
16843
|
+
lines.push(
|
|
16844
|
+
c.dim(
|
|
16845
|
+
` meta-provenance not written: owned by manifest ${record.manifest}, which manni meta fill does not write`
|
|
16846
|
+
)
|
|
16847
|
+
);
|
|
16848
|
+
} else if (record?.skipReason === "unwritable") {
|
|
16849
|
+
lines.push(
|
|
16850
|
+
c.dim(` meta-provenance not written: ${result.format} metadata cannot hold it`)
|
|
16851
|
+
);
|
|
16852
|
+
}
|
|
15179
16853
|
if (low.length > 0) {
|
|
15180
16854
|
lines.push(
|
|
15181
16855
|
c.dim(
|
|
@@ -15327,26 +17001,49 @@ function renderDerivePretty(run2, opts = {}) {
|
|
|
15327
17001
|
lines.push(`${result.file} ${c.dim("current")}${unknownNote(result, c.dim)}`);
|
|
15328
17002
|
continue;
|
|
15329
17003
|
}
|
|
15330
|
-
|
|
15331
|
-
const
|
|
15332
|
-
const changeWidth = Math.max(...pending.map((f) => change(f).length));
|
|
17004
|
+
const page = [];
|
|
17005
|
+
const held = /* @__PURE__ */ new Map();
|
|
15333
17006
|
for (const f of pending) {
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
const
|
|
15339
|
-
|
|
17007
|
+
if (f.ranges === void 0) {
|
|
17008
|
+
page.push(fieldRow(f, c));
|
|
17009
|
+
continue;
|
|
17010
|
+
}
|
|
17011
|
+
const rows = f.ranges.filter((r) => r.status !== "current").map((r) => rangeRow(f, r, c));
|
|
17012
|
+
if (f.manifest === void 0) page.push(...rows);
|
|
17013
|
+
else held.set(f.manifest, [...held.get(f.manifest) ?? [], ...rows]);
|
|
17014
|
+
}
|
|
17015
|
+
const all = [...page, ...[...held.values()].flat()];
|
|
17016
|
+
const nameWidth = Math.max(...all.map((r) => r.name.length));
|
|
17017
|
+
const changeWidth = Math.max(...all.map((r) => r.plain.length));
|
|
17018
|
+
const print = (row) => {
|
|
17019
|
+
const pad2 = " ".repeat(changeWidth - row.plain.length);
|
|
17020
|
+
lines.push(` ${c.cyan(row.name.padEnd(nameWidth))} ${row.change}${pad2} ${row.trail}`);
|
|
17021
|
+
};
|
|
17022
|
+
if (page.length > 0 || held.size === 0) lines.push(result.file);
|
|
17023
|
+
page.forEach(print);
|
|
17024
|
+
for (const [manifest, rows] of held) {
|
|
17025
|
+
lines.push(`${manifest} ${c.dim(`(for ${result.file})`)}`);
|
|
17026
|
+
rows.forEach(print);
|
|
15340
17027
|
}
|
|
15341
17028
|
const note = unknownNote(result, c.dim);
|
|
15342
17029
|
if (note !== "") lines.push(` ${note}`);
|
|
15343
17030
|
}
|
|
15344
17031
|
const s = run2.summary;
|
|
15345
17032
|
const parts = [plural2(s.files, "file")];
|
|
17033
|
+
const provenance = run2.results.flatMap((r) => r.fields.filter((f) => f.ranges !== void 0));
|
|
17034
|
+
const provenanceFields = provenance.filter((f) => run2.dryRun ? f.status === "stale" || f.status === "unset" : f.written).length;
|
|
15346
17035
|
if (run2.dryRun) {
|
|
15347
|
-
|
|
17036
|
+
const ranges = provenance.reduce((n, f) => n + (f.ranges ?? []).filter((r) => r.status !== "current").length, 0);
|
|
17037
|
+
const fields = s.stale + s.unset - provenanceFields;
|
|
17038
|
+
parts.push(`${s.changed} would change`);
|
|
17039
|
+
if (fields > 0 || ranges === 0) parts.push(plural2(fields, "field"));
|
|
17040
|
+
if (ranges > 0) parts.push(plural2(ranges, "range"));
|
|
15348
17041
|
} else {
|
|
15349
|
-
|
|
17042
|
+
const ranges = s.ranges ?? 0;
|
|
17043
|
+
const fields = s.written - provenanceFields;
|
|
17044
|
+
parts.push(`${s.changed} changed`);
|
|
17045
|
+
if (fields > 0 || ranges === 0) parts.push(`${plural2(fields, "field")} written`);
|
|
17046
|
+
if (ranges > 0) parts.push(`${plural2(ranges, "range")} written`);
|
|
15350
17047
|
}
|
|
15351
17048
|
if (s.errors > 0) parts.push(plural2(s.errors, "error"));
|
|
15352
17049
|
let footer = parts.join(", ");
|
|
@@ -15355,9 +17052,47 @@ function renderDerivePretty(run2, opts = {}) {
|
|
|
15355
17052
|
lines.push(s.errors > 0 ? c.yellow(footer) : footer);
|
|
15356
17053
|
return lines.join("\n");
|
|
15357
17054
|
}
|
|
15358
|
-
function
|
|
17055
|
+
function fieldRow(f, c) {
|
|
15359
17056
|
const from = f.status === "unset" ? "(unset)" : fmt2(f.asserted);
|
|
15360
|
-
|
|
17057
|
+
const to = fmt2(f.derived);
|
|
17058
|
+
return {
|
|
17059
|
+
name: f.field,
|
|
17060
|
+
change: `${c.dim(from)} \u2192 ${c.green(to)}`,
|
|
17061
|
+
plain: `${from} \u2192 ${to}`,
|
|
17062
|
+
trail: c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`)
|
|
17063
|
+
};
|
|
17064
|
+
}
|
|
17065
|
+
function rangeRow(f, r, c) {
|
|
17066
|
+
const head = `lines ${r.lines}: `;
|
|
17067
|
+
const machine = r["generated-by"];
|
|
17068
|
+
let from;
|
|
17069
|
+
let to;
|
|
17070
|
+
switch (r.status) {
|
|
17071
|
+
case "moved":
|
|
17072
|
+
from = "";
|
|
17073
|
+
to = `moved from ${r.from?.lines ?? r.lines}`;
|
|
17074
|
+
break;
|
|
17075
|
+
case "stale":
|
|
17076
|
+
from = r.from?.["generated-by"] ?? "(unset)";
|
|
17077
|
+
to = machine;
|
|
17078
|
+
break;
|
|
17079
|
+
case "changed":
|
|
17080
|
+
from = machine;
|
|
17081
|
+
to = "re-derived";
|
|
17082
|
+
break;
|
|
17083
|
+
case "unset":
|
|
17084
|
+
case "current":
|
|
17085
|
+
from = "(unset)";
|
|
17086
|
+
to = machine;
|
|
17087
|
+
break;
|
|
17088
|
+
}
|
|
17089
|
+
const arrow = from === "" ? "" : `${from} \u2192 `;
|
|
17090
|
+
return {
|
|
17091
|
+
name: f.field,
|
|
17092
|
+
change: `${head}${from === "" ? "" : `${c.dim(from)} \u2192 `}${c.green(to)}`,
|
|
17093
|
+
plain: `${head}${arrow}${to}`,
|
|
17094
|
+
trail: c.dim(`(${f.source ?? "git"}: ${r.evidence})`)
|
|
17095
|
+
};
|
|
15361
17096
|
}
|
|
15362
17097
|
function unknownNote(result, dim) {
|
|
15363
17098
|
const unknown = result.fields.filter((f) => f.status === "unknown").map((f) => f.field);
|
|
@@ -15375,6 +17110,15 @@ function stringifyValue(value) {
|
|
|
15375
17110
|
if (typeof value === "string") return value;
|
|
15376
17111
|
return JSON.stringify(value);
|
|
15377
17112
|
}
|
|
17113
|
+
function valueOf2(r, field, value) {
|
|
17114
|
+
if (field !== PROVENANCE_FIELD || r.provenance === void 0) return stringifyValue(value);
|
|
17115
|
+
if (value === void 0 || value === null) return "(unset)";
|
|
17116
|
+
const entries = provenanceEntries(value);
|
|
17117
|
+
if (!Array.isArray(value) || entries.length !== value.length || entries.length === 0) {
|
|
17118
|
+
return stringifyValue(value);
|
|
17119
|
+
}
|
|
17120
|
+
return renderProvenanceValue(entries, r.provenance.bodyLine);
|
|
17121
|
+
}
|
|
15378
17122
|
function evidenceOf(d) {
|
|
15379
17123
|
const repeated = ` (${stringifyValue(d.value)})`;
|
|
15380
17124
|
return d.evidence.endsWith(repeated) ? d.evidence.slice(0, -repeated.length) : d.evidence;
|
|
@@ -15386,6 +17130,10 @@ function annotation(r, field) {
|
|
|
15386
17130
|
if (origin === "derived") {
|
|
15387
17131
|
return d == null ? "" : ` (derived, ${d.source}: ${evidenceOf(d)})`;
|
|
15388
17132
|
}
|
|
17133
|
+
if (field === PROVENANCE_FIELD && r.provenance !== void 0) {
|
|
17134
|
+
if (r.provenance.current !== false) return " (asserted)";
|
|
17135
|
+
return ` (asserted; ${d?.source ?? "git"} says ${valueOf2(r, field, d?.value)})`;
|
|
17136
|
+
}
|
|
15389
17137
|
if (d == null) return " (asserted)";
|
|
15390
17138
|
if (compareDerived(field, r.resolved?.[field], d).status === "current") {
|
|
15391
17139
|
return " (asserted)";
|
|
@@ -15406,7 +17154,7 @@ function renderGet(results, fields, opts = {}) {
|
|
|
15406
17154
|
const note = annotation(r, f);
|
|
15407
17155
|
const suffix = note === "" ? "" : c.dim(note);
|
|
15408
17156
|
lines.push(
|
|
15409
|
-
`${c.dim(`${r.file}:`)} ${f}=${
|
|
17157
|
+
`${c.dim(`${r.file}:`)} ${f}=${valueOf2(r, f, effective[f])}${suffix}`
|
|
15410
17158
|
);
|
|
15411
17159
|
}
|
|
15412
17160
|
}
|
|
@@ -15520,7 +17268,8 @@ export {
|
|
|
15520
17268
|
ToolError,
|
|
15521
17269
|
errorMessage,
|
|
15522
17270
|
programName,
|
|
15523
|
-
|
|
17271
|
+
fail,
|
|
17272
|
+
runIfMain,
|
|
15524
17273
|
externalMetadataUrlProblem,
|
|
15525
17274
|
parseCollections,
|
|
15526
17275
|
selectCollections,
|
|
@@ -15530,8 +17279,6 @@ export {
|
|
|
15530
17279
|
generateEncryptionKey,
|
|
15531
17280
|
encryptValue,
|
|
15532
17281
|
decryptValue,
|
|
15533
|
-
KEYED_PIN_PREFIX,
|
|
15534
|
-
keyedPin,
|
|
15535
17282
|
findGitRoot,
|
|
15536
17283
|
notice,
|
|
15537
17284
|
findConfigFile,
|
|
@@ -15540,6 +17287,27 @@ export {
|
|
|
15540
17287
|
readFamilyConfigFile,
|
|
15541
17288
|
escapeWorkflowCommandMessage,
|
|
15542
17289
|
escapeWorkflowCommandProperty,
|
|
17290
|
+
INTEGRITY_SHAPE,
|
|
17291
|
+
integrityOf,
|
|
17292
|
+
isIntegrity,
|
|
17293
|
+
LineRangeError,
|
|
17294
|
+
normalizeText,
|
|
17295
|
+
splitLines,
|
|
17296
|
+
sliceLines,
|
|
17297
|
+
hashRange,
|
|
17298
|
+
hashLines,
|
|
17299
|
+
isKeyedPin,
|
|
17300
|
+
pinOfLines,
|
|
17301
|
+
MOVE_WINDOW_LINES,
|
|
17302
|
+
MOVE_BUDGET_BYTES,
|
|
17303
|
+
MAX_RANGE_LINES,
|
|
17304
|
+
findWindows,
|
|
17305
|
+
parseLines,
|
|
17306
|
+
lineSpec,
|
|
17307
|
+
spellLines,
|
|
17308
|
+
toFileLines,
|
|
17309
|
+
toBodyLines,
|
|
17310
|
+
splitPageArgument,
|
|
15543
17311
|
ENCRYPTION_KEY_ENV,
|
|
15544
17312
|
resolveEncryptionKey,
|
|
15545
17313
|
writeEncryptionKey,
|
|
@@ -15553,7 +17321,6 @@ export {
|
|
|
15553
17321
|
extractFrontmatter,
|
|
15554
17322
|
isErrorSeverity,
|
|
15555
17323
|
DocmetaError,
|
|
15556
|
-
deepEqual,
|
|
15557
17324
|
toJsonText,
|
|
15558
17325
|
applyFrontmatter,
|
|
15559
17326
|
extractorForExtension,
|
|
@@ -15570,9 +17337,6 @@ export {
|
|
|
15570
17337
|
DEFAULT_TTL_HOURS,
|
|
15571
17338
|
schemaCacheDir,
|
|
15572
17339
|
SchemaCache,
|
|
15573
|
-
INTEGRITY_SHAPE,
|
|
15574
|
-
integrityOf,
|
|
15575
|
-
isIntegrity,
|
|
15576
17340
|
listBuiltins,
|
|
15577
17341
|
classifyRef,
|
|
15578
17342
|
fetchSchemaBytes,
|
|
@@ -15581,7 +17345,6 @@ export {
|
|
|
15581
17345
|
resolveBaselineRequest,
|
|
15582
17346
|
settleBaseline,
|
|
15583
17347
|
DEFAULT_SCHEMAS,
|
|
15584
|
-
FILE_SCHEMA_KEY,
|
|
15585
17348
|
schemaEntryRef,
|
|
15586
17349
|
collectSchemaPins,
|
|
15587
17350
|
rebaseConfigSchemaRefs,
|
|
@@ -15629,6 +17392,7 @@ export {
|
|
|
15629
17392
|
orphanEntries,
|
|
15630
17393
|
orphanJoins,
|
|
15631
17394
|
orphanError,
|
|
17395
|
+
spliceManifestValue,
|
|
15632
17396
|
isParamName,
|
|
15633
17397
|
memberOf,
|
|
15634
17398
|
Validator,
|
|
@@ -15669,6 +17433,7 @@ export {
|
|
|
15669
17433
|
runQuery,
|
|
15670
17434
|
runFill,
|
|
15671
17435
|
runDerive,
|
|
17436
|
+
deriveFailed,
|
|
15672
17437
|
FILL_FORMATS,
|
|
15673
17438
|
FILL_FORMAT_LIST,
|
|
15674
17439
|
isFillFormat,
|
|
@@ -15683,4 +17448,4 @@ export {
|
|
|
15683
17448
|
renderQuery,
|
|
15684
17449
|
renderQueryCsv
|
|
15685
17450
|
};
|
|
15686
|
-
//# sourceMappingURL=chunk-
|
|
17451
|
+
//# sourceMappingURL=chunk-2DC2GSRH.js.map
|