@hawkeyexl/manni 0.3.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BKJOQHWN.js → chunk-FL75S2L4.js} +38 -3
- package/dist/chunk-FL75S2L4.js.map +1 -0
- package/dist/{chunk-7M2YOXVG.js → chunk-IRHWILCO.js} +1130 -784
- package/dist/chunk-IRHWILCO.js.map +1 -0
- package/dist/cli.js +71 -11
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +275 -115
- package/dist/index.js +31 -23
- package/package.json +1 -1
- package/dist/chunk-7M2YOXVG.js.map +0 -1
- package/dist/chunk-BKJOQHWN.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hawkeyexl/manni",
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "1.0.1",
|
|
7
7
|
description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
|
|
8
8
|
type: "module",
|
|
9
9
|
bin: {
|
|
@@ -117,6 +117,276 @@ var package_default = {
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
// src/shared/collections.ts
|
|
121
|
+
import picomatch2 from "picomatch";
|
|
122
|
+
|
|
123
|
+
// src/shared/globs.ts
|
|
124
|
+
import picomatch from "picomatch";
|
|
125
|
+
var matcherCache = /* @__PURE__ */ new Map();
|
|
126
|
+
function matchesFileGlob(relPath, glob) {
|
|
127
|
+
const key = JSON.stringify(glob);
|
|
128
|
+
let m = matcherCache.get(key);
|
|
129
|
+
if (!m) {
|
|
130
|
+
m = picomatch(glob, { dot: true });
|
|
131
|
+
matcherCache.set(key, m);
|
|
132
|
+
}
|
|
133
|
+
return m(relPath.replace(/\\/g, "/"));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/shared/collections.ts
|
|
137
|
+
var COLLECTION_KEYS = [
|
|
138
|
+
"name",
|
|
139
|
+
"paths",
|
|
140
|
+
"exclude",
|
|
141
|
+
"url",
|
|
142
|
+
"externalMetadata"
|
|
143
|
+
];
|
|
144
|
+
var EXTERNAL_METADATA_KEYS = ["file", "keys", "tokenEnv", "join"];
|
|
145
|
+
var SCHEMA_KEY = "$schema";
|
|
146
|
+
var STDIN = "<stdin>";
|
|
147
|
+
function isUrlRef(ref) {
|
|
148
|
+
return /^https?:\/\//i.test(ref);
|
|
149
|
+
}
|
|
150
|
+
function externalMetadataUrlProblem(url) {
|
|
151
|
+
let parsed2;
|
|
152
|
+
try {
|
|
153
|
+
parsed2 = new URL(url);
|
|
154
|
+
} catch {
|
|
155
|
+
return "is not a valid URL";
|
|
156
|
+
}
|
|
157
|
+
if (parsed2.username !== "" || parsed2.password !== "") {
|
|
158
|
+
return 'carries a credential in the URL; put the token in an environment variable and name it with "tokenEnv"';
|
|
159
|
+
}
|
|
160
|
+
if (parsed2.protocol === "https:") return null;
|
|
161
|
+
if (parsed2.protocol === "http:" && isLoopback(parsed2.hostname)) return null;
|
|
162
|
+
if (parsed2.protocol === "http:") {
|
|
163
|
+
return "is plain http://; a bearer token over plaintext is a leak, and a public manifest is served over https too";
|
|
164
|
+
}
|
|
165
|
+
return `has the unsupported scheme "${parsed2.protocol}"`;
|
|
166
|
+
}
|
|
167
|
+
function isLoopback(host) {
|
|
168
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1" || host.startsWith("127.");
|
|
169
|
+
}
|
|
170
|
+
function rejectUnknownKeys(raw, allowed, where, source, toError) {
|
|
171
|
+
for (const key of Object.keys(raw)) {
|
|
172
|
+
if (!allowed.includes(key)) {
|
|
173
|
+
throw toError(
|
|
174
|
+
`${source}: ${where} has unknown key "${key}". Supported keys: ${allowed.join(", ")}.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function isMapping(value) {
|
|
180
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
181
|
+
}
|
|
182
|
+
function isNonEmptyString(value) {
|
|
183
|
+
return typeof value === "string" && value.trim() !== "";
|
|
184
|
+
}
|
|
185
|
+
function parseCollections(raw, source, toError) {
|
|
186
|
+
if (!Array.isArray(raw)) {
|
|
187
|
+
throw toError(`${source}: "collections" must be a list.`);
|
|
188
|
+
}
|
|
189
|
+
if (raw.length === 0) {
|
|
190
|
+
throw toError(
|
|
191
|
+
`${source}: "collections" must name at least one collection; remove the key if there are none.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const taken = /* @__PURE__ */ new Map();
|
|
195
|
+
return raw.map((entry, i) => {
|
|
196
|
+
const where = `collections[${i}]`;
|
|
197
|
+
if (!isMapping(entry)) {
|
|
198
|
+
throw toError(`${source}: ${where} must be a mapping.`);
|
|
199
|
+
}
|
|
200
|
+
rejectUnknownKeys(entry, COLLECTION_KEYS, where, source, toError);
|
|
201
|
+
if (!isNonEmptyString(entry.name)) {
|
|
202
|
+
throw toError(`${source}: ${where}.name must be a non-empty string.`);
|
|
203
|
+
}
|
|
204
|
+
const name2 = entry.name;
|
|
205
|
+
const folded = name2.toLowerCase();
|
|
206
|
+
if (folded === "docs") {
|
|
207
|
+
throw toError(
|
|
208
|
+
`${source}: ${where}.name "${name2}" collides with the docs table every query reads. Pick another name, such as "site" or "pages".`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (folded.startsWith("sqlite_")) {
|
|
212
|
+
throw toError(
|
|
213
|
+
`${source}: ${where}.name "${name2}" starts with "sqlite_", which SQLite reserves for its own objects. Pick another name.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const owner = taken.get(folded);
|
|
217
|
+
if (owner !== void 0) {
|
|
218
|
+
throw toError(
|
|
219
|
+
`${source}: ${where}.name "${name2}" is already taken by collections[${owner}]; names are compared case-insensitively because they become SQL views.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
taken.set(folded, i);
|
|
223
|
+
if (!Array.isArray(entry.paths) || entry.paths.length === 0 || entry.paths.some((p) => !isNonEmptyString(p))) {
|
|
224
|
+
throw toError(
|
|
225
|
+
`${source}: ${where}.paths must be a non-empty list of files, directories or globs.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const paths = entry.paths;
|
|
229
|
+
let exclude = [];
|
|
230
|
+
if (entry.exclude !== void 0) {
|
|
231
|
+
if (!Array.isArray(entry.exclude) || entry.exclude.some((g) => !isNonEmptyString(g))) {
|
|
232
|
+
throw toError(`${source}: ${where}.exclude must be a list of globs.`);
|
|
233
|
+
}
|
|
234
|
+
exclude = entry.exclude;
|
|
235
|
+
}
|
|
236
|
+
let url;
|
|
237
|
+
if (entry.url !== void 0) {
|
|
238
|
+
if (typeof entry.url !== "string" || !isHttpUrl(entry.url)) {
|
|
239
|
+
throw toError(`${source}: ${where}.url must be an http(s) URL.`);
|
|
240
|
+
}
|
|
241
|
+
url = entry.url;
|
|
242
|
+
}
|
|
243
|
+
let externalMetadata = [];
|
|
244
|
+
if (entry.externalMetadata !== void 0) {
|
|
245
|
+
if (!Array.isArray(entry.externalMetadata)) {
|
|
246
|
+
throw toError(`${source}: ${where}.externalMetadata must be a list.`);
|
|
247
|
+
}
|
|
248
|
+
externalMetadata = parseExternalMetadata(
|
|
249
|
+
entry.externalMetadata,
|
|
250
|
+
where,
|
|
251
|
+
source,
|
|
252
|
+
toError
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
name: name2,
|
|
257
|
+
paths,
|
|
258
|
+
exclude,
|
|
259
|
+
externalMetadata,
|
|
260
|
+
...url === void 0 ? {} : { url }
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function isHttpUrl(value) {
|
|
265
|
+
let parsed2;
|
|
266
|
+
try {
|
|
267
|
+
parsed2 = new URL(value);
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return parsed2.protocol === "http:" || parsed2.protocol === "https:";
|
|
272
|
+
}
|
|
273
|
+
function parseExternalMetadata(raw, collectionWhere, source, toError) {
|
|
274
|
+
const owners = /* @__PURE__ */ new Map();
|
|
275
|
+
return raw.map((entry, i) => {
|
|
276
|
+
const where = `${collectionWhere}.externalMetadata[${i}]`;
|
|
277
|
+
if (!isMapping(entry)) {
|
|
278
|
+
throw toError(`${source}: ${where} must be a mapping.`);
|
|
279
|
+
}
|
|
280
|
+
rejectUnknownKeys(entry, EXTERNAL_METADATA_KEYS, where, source, toError);
|
|
281
|
+
if (!isNonEmptyString(entry.file)) {
|
|
282
|
+
throw toError(
|
|
283
|
+
`${source}: ${where}.file must be a non-empty string naming the manifest, relative to the config file.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (!Array.isArray(entry.keys) || entry.keys.length === 0 || entry.keys.some((k) => !isNonEmptyString(k))) {
|
|
287
|
+
throw toError(
|
|
288
|
+
`${source}: ${where}.keys must be a non-empty list of key names.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const isUrl = isUrlRef(entry.file);
|
|
292
|
+
if (isUrl) {
|
|
293
|
+
const problem = externalMetadataUrlProblem(entry.file);
|
|
294
|
+
if (problem !== null) {
|
|
295
|
+
throw toError(`${source}: ${where}.file ${problem}.`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (entry.tokenEnv !== void 0) {
|
|
299
|
+
if (!isNonEmptyString(entry.tokenEnv)) {
|
|
300
|
+
throw toError(
|
|
301
|
+
`${source}: ${where}.tokenEnv must be the name of an environment variable.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (!isUrl) {
|
|
305
|
+
throw toError(
|
|
306
|
+
`${source}: ${where}.tokenEnv is set, but "file" is a path, so no request is made and the token would go nowhere. Remove it, or make "file" a URL.`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const keys = entry.keys;
|
|
311
|
+
if (entry.join !== void 0) {
|
|
312
|
+
if (!isNonEmptyString(entry.join)) {
|
|
313
|
+
throw toError(
|
|
314
|
+
`${source}: ${where}.join must be "path" or the name of a top-level frontmatter field.`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (entry.join === SCHEMA_KEY) {
|
|
318
|
+
throw toError(`${source}: ${where}.join may not be "${SCHEMA_KEY}".`);
|
|
319
|
+
}
|
|
320
|
+
if (keys.includes(entry.join)) {
|
|
321
|
+
throw toError(
|
|
322
|
+
`${source}: ${where}.join names "${entry.join}", which the same entry owns \u2014 the value that selects an entry cannot come from the entry.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
327
|
+
for (const key of keys) {
|
|
328
|
+
if (key === SCHEMA_KEY) {
|
|
329
|
+
throw toError(
|
|
330
|
+
`${source}: ${where}.keys may not include "${SCHEMA_KEY}" \u2014 a manifest never chooses the schema a document is judged by; use "overrides".`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (seen.has(key)) {
|
|
334
|
+
throw toError(`${source}: ${where}.keys lists "${key}" twice.`);
|
|
335
|
+
}
|
|
336
|
+
seen.add(key);
|
|
337
|
+
const owner = owners.get(key);
|
|
338
|
+
if (owner !== void 0) {
|
|
339
|
+
throw toError(
|
|
340
|
+
`${source}: ${where}.keys claims "${key}", which externalMetadata[${owner}] already owns \u2014 a key has exactly one manifest in a collection.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
owners.set(key, i);
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
file: entry.file,
|
|
347
|
+
keys,
|
|
348
|
+
...typeof entry.tokenEnv === "string" ? { tokenEnv: entry.tokenEnv } : {},
|
|
349
|
+
...typeof entry.join === "string" ? { join: entry.join } : {}
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function selectCollections(collections, names, source, toError) {
|
|
354
|
+
if (names === void 0 || names.length === 0) return [...collections];
|
|
355
|
+
const wanted = new Set(names);
|
|
356
|
+
for (const name2 of wanted) {
|
|
357
|
+
if (!collections.some((c) => c.name === name2)) {
|
|
358
|
+
const configured = collections.length === 0 ? "(none)" : collections.map((c) => c.name).join(", ");
|
|
359
|
+
const nearMiss = collections.find(
|
|
360
|
+
(c) => c.name.toLowerCase() === name2.toLowerCase()
|
|
361
|
+
);
|
|
362
|
+
const hint = nearMiss === void 0 ? "" : ` Names are case-sensitive; did you mean "${nearMiss.name}"?`;
|
|
363
|
+
throw toError(
|
|
364
|
+
`no collection named "${name2}" in ${source}. Configured: ${configured}.${hint}`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return collections.filter((c) => wanted.has(c.name));
|
|
369
|
+
}
|
|
370
|
+
function isMember(collection, relPath) {
|
|
371
|
+
if (relPath === STDIN) return false;
|
|
372
|
+
if (relPath.startsWith("../")) return false;
|
|
373
|
+
const matched = collection.paths.some((entry) => {
|
|
374
|
+
const normalized = normalizeEntry(entry);
|
|
375
|
+
if (normalized === "") return false;
|
|
376
|
+
if (isGlob(normalized)) return matchesFileGlob(relPath, normalized);
|
|
377
|
+
return relPath === normalized || relPath.startsWith(`${normalized}/`);
|
|
378
|
+
});
|
|
379
|
+
if (!matched) return false;
|
|
380
|
+
return !collection.exclude.some((glob) => matchesFileGlob(relPath, glob));
|
|
381
|
+
}
|
|
382
|
+
function normalizeEntry(entry) {
|
|
383
|
+
const posix2 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
384
|
+
return posix2.endsWith("/") ? posix2.slice(0, -1) : posix2;
|
|
385
|
+
}
|
|
386
|
+
function isGlob(entry) {
|
|
387
|
+
return picomatch2.scan(entry).isGlob;
|
|
388
|
+
}
|
|
389
|
+
|
|
120
390
|
// src/shared/errors.ts
|
|
121
391
|
var ToolError = class extends Error {
|
|
122
392
|
/** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
|
|
@@ -126,6 +396,19 @@ var ToolError = class extends Error {
|
|
|
126
396
|
this.name = "ToolError";
|
|
127
397
|
}
|
|
128
398
|
};
|
|
399
|
+
function errorMessage(err) {
|
|
400
|
+
if (err instanceof Error) return err.message;
|
|
401
|
+
if (typeof err === "string") return err;
|
|
402
|
+
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
403
|
+
return err.message;
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const json = JSON.stringify(err);
|
|
407
|
+
if (json !== void 0) return json;
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
return String(err);
|
|
411
|
+
}
|
|
129
412
|
|
|
130
413
|
// src/meta/types.ts
|
|
131
414
|
var DocmetaError = class extends ToolError {
|
|
@@ -2617,9 +2900,9 @@ function assertPublishableBuiltinId(id) {
|
|
|
2617
2900
|
`Built-in id "${id}" is not publishable: the "check" first segment is reserved for corpus check identities (check:<name>, proposal 0026).`
|
|
2618
2901
|
);
|
|
2619
2902
|
}
|
|
2620
|
-
if (first === "
|
|
2903
|
+
if (first === "external") {
|
|
2621
2904
|
throw new Error(
|
|
2622
|
-
`Built-in id "${id}" is not publishable: the "
|
|
2905
|
+
`Built-in id "${id}" is not publishable: the "external" first segment is reserved for external-metadata finding identities (external:owned, external:duplicate, proposal 0037).`
|
|
2623
2906
|
);
|
|
2624
2907
|
}
|
|
2625
2908
|
}
|
|
@@ -2843,7 +3126,7 @@ async function requestSchema(ref, timeoutMs, timedOut) {
|
|
|
2843
3126
|
} catch (err) {
|
|
2844
3127
|
if (isAbort(err)) throw timedOut;
|
|
2845
3128
|
throw new RetryableFetchError(
|
|
2846
|
-
`Failed to fetch schema "${ref}": ${err
|
|
3129
|
+
`Failed to fetch schema "${ref}": ${errorMessage(err)}`
|
|
2847
3130
|
);
|
|
2848
3131
|
}
|
|
2849
3132
|
if (!res.ok) {
|
|
@@ -2875,7 +3158,7 @@ async function fetchSchemaBytes(ref, options = {}) {
|
|
|
2875
3158
|
if (err instanceof DocmetaError) throw err;
|
|
2876
3159
|
if (isAbort(err)) throw timedOut;
|
|
2877
3160
|
throw new DocmetaError(
|
|
2878
|
-
`Failed to fetch schema "${ref}": ${err
|
|
3161
|
+
`Failed to fetch schema "${ref}": ${errorMessage(err)}`
|
|
2879
3162
|
);
|
|
2880
3163
|
}
|
|
2881
3164
|
const raw = stripBom(bytes.toString("utf8"));
|
|
@@ -2884,7 +3167,7 @@ async function fetchSchemaBytes(ref, options = {}) {
|
|
|
2884
3167
|
parsed2 = JSON.parse(raw);
|
|
2885
3168
|
} catch (err) {
|
|
2886
3169
|
throw new DocmetaError(
|
|
2887
|
-
`Schema "${ref}" did not return valid JSON: ${err
|
|
3170
|
+
`Schema "${ref}" did not return valid JSON: ${errorMessage(err)}`
|
|
2888
3171
|
);
|
|
2889
3172
|
}
|
|
2890
3173
|
return { bytes, schema: assertFetchedSchema(ref, parsed2, raw) };
|
|
@@ -2997,14 +3280,13 @@ async function loadSchema(ref, options = {}) {
|
|
|
2997
3280
|
return JSON.parse(text);
|
|
2998
3281
|
} catch (err) {
|
|
2999
3282
|
throw new DocmetaError(
|
|
3000
|
-
`Schema file "${ref}" is not valid JSON: ${withoutFileExcerpt(err
|
|
3283
|
+
`Schema file "${ref}" is not valid JSON: ${withoutFileExcerpt(errorMessage(err), text)}`
|
|
3001
3284
|
);
|
|
3002
3285
|
}
|
|
3003
3286
|
}
|
|
3004
3287
|
|
|
3005
3288
|
// src/meta/core/resolve-schema.ts
|
|
3006
3289
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3007
|
-
import picomatch from "picomatch";
|
|
3008
3290
|
var DEFAULT_SCHEMAS = Object.freeze([
|
|
3009
3291
|
"google:okf:0.1",
|
|
3010
3292
|
"passo-uno:seven-action:1.0"
|
|
@@ -3044,21 +3326,15 @@ function rebaseConfigSchemaRefs(config, configDir, cwd) {
|
|
|
3044
3326
|
} : {}
|
|
3045
3327
|
};
|
|
3046
3328
|
}
|
|
3047
|
-
var matcherCache = /* @__PURE__ */ new Map();
|
|
3048
3329
|
function overrideGlobs(files) {
|
|
3330
|
+
if (files === void 0) return [];
|
|
3049
3331
|
return typeof files === "string" ? [files] : files;
|
|
3050
3332
|
}
|
|
3051
|
-
function
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
function matches(glob, filePath) {
|
|
3055
|
-
const key = JSON.stringify(glob);
|
|
3056
|
-
let m = matcherCache.get(key);
|
|
3057
|
-
if (!m) {
|
|
3058
|
-
m = picomatch(glob, { dot: true });
|
|
3059
|
-
matcherCache.set(key, m);
|
|
3333
|
+
function overrideMatches(override, filePath, memberOf2) {
|
|
3334
|
+
if (override.collection !== void 0) {
|
|
3335
|
+
return memberOf2.includes(override.collection);
|
|
3060
3336
|
}
|
|
3061
|
-
return
|
|
3337
|
+
return override.files !== void 0 && matchesFileGlob(filePath, override.files);
|
|
3062
3338
|
}
|
|
3063
3339
|
function coerceFileSchema(value) {
|
|
3064
3340
|
if (value === void 0 || value === null) return void 0;
|
|
@@ -3136,8 +3412,9 @@ function resolveSchemaSetWithSource(params) {
|
|
|
3136
3412
|
}
|
|
3137
3413
|
}
|
|
3138
3414
|
if (config?.overrides) {
|
|
3415
|
+
const memberOf2 = params.memberOf ?? [];
|
|
3139
3416
|
for (const [i, ov] of config.overrides.entries()) {
|
|
3140
|
-
if (
|
|
3417
|
+
if (overrideMatches(ov, filePath, memberOf2) && ov.schemas.length > 0) {
|
|
3141
3418
|
return { schemas: dedupe(ov.schemas), source: "override", overrideIndex: i };
|
|
3142
3419
|
}
|
|
3143
3420
|
}
|
|
@@ -3153,55 +3430,37 @@ function resolveSchemaSetWithSource(params) {
|
|
|
3153
3430
|
function resolveSchemaSet(params) {
|
|
3154
3431
|
return resolveSchemaSetWithSource(params).schemas;
|
|
3155
3432
|
}
|
|
3156
|
-
function resolveElements(filePath, config) {
|
|
3433
|
+
function resolveElements(filePath, config, memberOf2 = []) {
|
|
3157
3434
|
const out = [];
|
|
3158
3435
|
if (config?.elements) out.push(...config.elements);
|
|
3159
3436
|
for (const ov of config?.overrides ?? []) {
|
|
3160
|
-
if (ov.elements &&
|
|
3437
|
+
if (ov.elements && overrideMatches(ov, filePath, memberOf2)) {
|
|
3438
|
+
out.push(...ov.elements);
|
|
3439
|
+
}
|
|
3161
3440
|
}
|
|
3162
3441
|
return dedupe(out);
|
|
3163
3442
|
}
|
|
3164
3443
|
|
|
3165
|
-
// src/meta/core/
|
|
3166
|
-
var
|
|
3167
|
-
var
|
|
3444
|
+
// src/meta/core/external-metadata-fetch.ts
|
|
3445
|
+
var EXTERNAL_METADATA_FETCH_TIMEOUT_MS = 1e4;
|
|
3446
|
+
var EXTERNAL_METADATA_FETCH_MAX_BYTES = 5 * 1024 * 1024;
|
|
3168
3447
|
var MAX_REDIRECTS = 5;
|
|
3169
3448
|
var RETRY_DELAY_MS2 = 500;
|
|
3170
|
-
function
|
|
3171
|
-
let parsed2;
|
|
3172
|
-
try {
|
|
3173
|
-
parsed2 = new URL(url);
|
|
3174
|
-
} catch {
|
|
3175
|
-
return "is not a valid URL";
|
|
3176
|
-
}
|
|
3177
|
-
if (parsed2.username !== "" || parsed2.password !== "") {
|
|
3178
|
-
return 'carries a credential in the URL; put the token in an environment variable and name it with "tokenEnv"';
|
|
3179
|
-
}
|
|
3180
|
-
if (parsed2.protocol === "https:") return null;
|
|
3181
|
-
if (parsed2.protocol === "http:" && isLoopback(parsed2.hostname)) return null;
|
|
3182
|
-
if (parsed2.protocol === "http:") {
|
|
3183
|
-
return "is plain http://; a bearer token over plaintext is a leak, and a public manifest is served over https too";
|
|
3184
|
-
}
|
|
3185
|
-
return `has the unsupported scheme "${parsed2.protocol}"`;
|
|
3186
|
-
}
|
|
3187
|
-
function isLoopback(host) {
|
|
3188
|
-
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1" || host.startsWith("127.");
|
|
3189
|
-
}
|
|
3190
|
-
async function fetchSidecar(url, opts = {}) {
|
|
3449
|
+
async function fetchExternalMetadata(url, opts = {}) {
|
|
3191
3450
|
if (opts.offline) {
|
|
3192
3451
|
throw new DocmetaError(
|
|
3193
|
-
`
|
|
3452
|
+
`Manifest ${url} is remote and the run is offline. Vendor it to a path, or drop --offline.`
|
|
3194
3453
|
);
|
|
3195
3454
|
}
|
|
3196
|
-
const timeoutMs = opts.timeoutMs ??
|
|
3197
|
-
const maxBytes = opts.maxBytes ??
|
|
3455
|
+
const timeoutMs = opts.timeoutMs ?? EXTERNAL_METADATA_FETCH_TIMEOUT_MS;
|
|
3456
|
+
const maxBytes = opts.maxBytes ?? EXTERNAL_METADATA_FETCH_MAX_BYTES;
|
|
3198
3457
|
const env = opts.env ?? process.env;
|
|
3199
3458
|
let token;
|
|
3200
3459
|
if (opts.tokenEnv !== void 0) {
|
|
3201
3460
|
token = env[opts.tokenEnv];
|
|
3202
3461
|
if (token === void 0 || token === "") {
|
|
3203
3462
|
throw new DocmetaError(
|
|
3204
|
-
`
|
|
3463
|
+
`Manifest ${url}: the environment variable ${opts.tokenEnv} named by "tokenEnv" is not set.`
|
|
3205
3464
|
);
|
|
3206
3465
|
}
|
|
3207
3466
|
}
|
|
@@ -3212,13 +3471,13 @@ async function fetchSidecar(url, opts = {}) {
|
|
|
3212
3471
|
}
|
|
3213
3472
|
if (res instanceof Error) {
|
|
3214
3473
|
throw new DocmetaError(
|
|
3215
|
-
`
|
|
3474
|
+
`Manifest ${url} could not be fetched: ${res.message}`
|
|
3216
3475
|
);
|
|
3217
3476
|
}
|
|
3218
3477
|
if (!res.ok) {
|
|
3219
3478
|
const hint = res.status === 404 && token === void 0 ? ' (a private file answers 404 without a token; set "tokenEnv")' : res.status === 404 ? " (a private file answers 404 without a valid token)" : "";
|
|
3220
3479
|
throw new DocmetaError(
|
|
3221
|
-
`
|
|
3480
|
+
`Manifest ${url} could not be fetched: HTTP ${res.status}${hint}.`
|
|
3222
3481
|
);
|
|
3223
3482
|
}
|
|
3224
3483
|
return readCapped(url, res, maxBytes);
|
|
@@ -3265,7 +3524,7 @@ async function readCapped(url, res, maxBytes) {
|
|
|
3265
3524
|
if (total > maxBytes) {
|
|
3266
3525
|
await reader.cancel();
|
|
3267
3526
|
throw new DocmetaError(
|
|
3268
|
-
`
|
|
3527
|
+
`Manifest ${url} is too large: the response exceeds the ${maxBytes}-byte limit.`
|
|
3269
3528
|
);
|
|
3270
3529
|
}
|
|
3271
3530
|
chunks.push(value);
|
|
@@ -4933,7 +5192,7 @@ function applyXml(original, patch, options = {}) {
|
|
|
4933
5192
|
before = readXml(original, options.filePath, { elements: options.elements });
|
|
4934
5193
|
} catch (err) {
|
|
4935
5194
|
throw new DocmetaError(
|
|
4936
|
-
`Refusing to write metadata: ${err
|
|
5195
|
+
`Refusing to write metadata: ${errorMessage(err)}`
|
|
4937
5196
|
);
|
|
4938
5197
|
}
|
|
4939
5198
|
const content = before.body;
|
|
@@ -5068,7 +5327,7 @@ function verify2(next, before, patch, options) {
|
|
|
5068
5327
|
}).data;
|
|
5069
5328
|
} catch (err) {
|
|
5070
5329
|
throw new DocmetaError(
|
|
5071
|
-
`Refusing to write XML metadata: the rewritten document did not parse (${err
|
|
5330
|
+
`Refusing to write XML metadata: the rewritten document did not parse (${errorMessage(err)}).`
|
|
5072
5331
|
);
|
|
5073
5332
|
}
|
|
5074
5333
|
if (!deepEqual(actual, expected)) {
|
|
@@ -5458,7 +5717,7 @@ function listFormats() {
|
|
|
5458
5717
|
}));
|
|
5459
5718
|
}
|
|
5460
5719
|
|
|
5461
|
-
// src/meta/core/
|
|
5720
|
+
// src/meta/core/external-metadata.ts
|
|
5462
5721
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
5463
5722
|
import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
5464
5723
|
import {
|
|
@@ -5473,7 +5732,7 @@ import {
|
|
|
5473
5732
|
import { stat as stat3 } from "node:fs/promises";
|
|
5474
5733
|
import { extname, relative as relative3, resolve as resolve4 } from "node:path";
|
|
5475
5734
|
import fg from "fast-glob";
|
|
5476
|
-
import
|
|
5735
|
+
import picomatch3 from "picomatch";
|
|
5477
5736
|
|
|
5478
5737
|
// src/meta/core/gitignore.ts
|
|
5479
5738
|
import { spawn } from "node:child_process";
|
|
@@ -5648,7 +5907,7 @@ async function resolveTargetSet(opts) {
|
|
|
5648
5907
|
for (const f of found2) if (keepByExt(f)) walked.add(f);
|
|
5649
5908
|
continue;
|
|
5650
5909
|
}
|
|
5651
|
-
if (!
|
|
5910
|
+
if (!picomatch3.scan(posixInput).isGlob) {
|
|
5652
5911
|
missing.push(posixInput);
|
|
5653
5912
|
continue;
|
|
5654
5913
|
}
|
|
@@ -5708,44 +5967,50 @@ function assertNonEmpty(p) {
|
|
|
5708
5967
|
);
|
|
5709
5968
|
}
|
|
5710
5969
|
|
|
5711
|
-
// src/meta/core/
|
|
5712
|
-
var
|
|
5713
|
-
var
|
|
5714
|
-
var
|
|
5970
|
+
// src/meta/core/external-metadata.ts
|
|
5971
|
+
var EXTERNAL_OWNED_SCHEMA = "external:owned";
|
|
5972
|
+
var EXTERNAL_DUPLICATE_SCHEMA = "external:duplicate";
|
|
5973
|
+
var EXTERNAL_KEYWORD = "external";
|
|
5715
5974
|
var PATH_JOIN = "path";
|
|
5716
5975
|
var posix = (p) => p.split(sep2).join("/");
|
|
5717
5976
|
function reportedPath(abs, base) {
|
|
5718
5977
|
const rel = relative4(base, abs);
|
|
5719
5978
|
return rel === "" ? "." : posix(rel);
|
|
5720
5979
|
}
|
|
5721
|
-
function
|
|
5722
|
-
return
|
|
5980
|
+
function externalMetadataJoin(manifest) {
|
|
5981
|
+
return manifest.join ?? PATH_JOIN;
|
|
5723
5982
|
}
|
|
5724
|
-
async function
|
|
5725
|
-
const configured =
|
|
5726
|
-
|
|
5983
|
+
async function loadExternalMetadata(collections, opts) {
|
|
5984
|
+
const configured = collections.flatMap(
|
|
5985
|
+
(c) => c.externalMetadata.map((m) => ({ collection: c.name, manifest: m }))
|
|
5986
|
+
);
|
|
5987
|
+
if (configured.length === 0) return null;
|
|
5727
5988
|
const owners = /* @__PURE__ */ new Map();
|
|
5728
5989
|
const byPath = /* @__PURE__ */ new Map();
|
|
5729
5990
|
const byField = /* @__PURE__ */ new Map();
|
|
5730
5991
|
const entries = [];
|
|
5731
|
-
for (const
|
|
5992
|
+
for (const { collection, manifest } of configured) {
|
|
5732
5993
|
let text;
|
|
5733
5994
|
let file;
|
|
5734
|
-
if (classifyRef(
|
|
5735
|
-
file =
|
|
5736
|
-
text = await
|
|
5737
|
-
...
|
|
5995
|
+
if (classifyRef(manifest.file).kind === "url") {
|
|
5996
|
+
file = manifest.file;
|
|
5997
|
+
text = await fetchExternalMetadata(manifest.file, {
|
|
5998
|
+
...manifest.tokenEnv !== void 0 ? { tokenEnv: manifest.tokenEnv } : {},
|
|
5738
5999
|
...opts.offline !== void 0 ? { offline: opts.offline } : {},
|
|
5739
6000
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
5740
6001
|
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
5741
6002
|
});
|
|
5742
6003
|
} else {
|
|
5743
|
-
const abs = isAbsolute2(
|
|
6004
|
+
const abs = isAbsolute2(manifest.file) ? manifest.file : resolve5(opts.configDir, manifest.file);
|
|
5744
6005
|
file = reportedPath(abs, opts.base);
|
|
5745
6006
|
text = await readManifest(abs, file);
|
|
5746
6007
|
}
|
|
5747
|
-
for (const key of
|
|
5748
|
-
|
|
6008
|
+
for (const key of manifest.keys) {
|
|
6009
|
+
const list = owners.get(key);
|
|
6010
|
+
if (list) list.push({ collection, file });
|
|
6011
|
+
else owners.set(key, [{ collection, file }]);
|
|
6012
|
+
}
|
|
6013
|
+
const join7 = externalMetadataJoin(manifest);
|
|
5749
6014
|
let target;
|
|
5750
6015
|
if (join7 === PATH_JOIN) {
|
|
5751
6016
|
target = byPath;
|
|
@@ -5757,7 +6022,16 @@ async function loadSidecars(config, opts) {
|
|
|
5757
6022
|
byField.set(join7, target);
|
|
5758
6023
|
}
|
|
5759
6024
|
}
|
|
5760
|
-
parseManifest(
|
|
6025
|
+
parseManifest(
|
|
6026
|
+
manifest,
|
|
6027
|
+
collection,
|
|
6028
|
+
text,
|
|
6029
|
+
file,
|
|
6030
|
+
opts.configDir,
|
|
6031
|
+
join7,
|
|
6032
|
+
target,
|
|
6033
|
+
entries
|
|
6034
|
+
);
|
|
5761
6035
|
}
|
|
5762
6036
|
return { owners, byPath, byField, entries };
|
|
5763
6037
|
}
|
|
@@ -5767,27 +6041,27 @@ async function readManifest(abs, file) {
|
|
|
5767
6041
|
} catch (err) {
|
|
5768
6042
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5769
6043
|
throw new DocmetaError(
|
|
5770
|
-
`
|
|
6044
|
+
`Manifest ${file} could not be read: ${reason}`
|
|
5771
6045
|
);
|
|
5772
6046
|
}
|
|
5773
6047
|
}
|
|
5774
|
-
function parseManifest(
|
|
6048
|
+
function parseManifest(manifest, collection, text, file, configDir, join7, target, entries) {
|
|
5775
6049
|
const lc = new LineCounter2();
|
|
5776
6050
|
const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
|
|
5777
6051
|
const problem = doc.errors[0];
|
|
5778
6052
|
if (problem) {
|
|
5779
6053
|
throw new DocmetaError(
|
|
5780
|
-
`
|
|
6054
|
+
`Manifest ${file} is not valid YAML: ${problem.message}`
|
|
5781
6055
|
);
|
|
5782
6056
|
}
|
|
5783
6057
|
const root = doc.contents;
|
|
5784
6058
|
if (root === null || isScalar2(root) && root.value === null) return;
|
|
5785
6059
|
if (!isMap3(root)) {
|
|
5786
6060
|
throw new DocmetaError(
|
|
5787
|
-
`
|
|
6061
|
+
`Manifest ${file}: the manifest must be a mapping from document ${join7 === PATH_JOIN ? "path" : `"${join7}"`} to owned keys.`
|
|
5788
6062
|
);
|
|
5789
6063
|
}
|
|
5790
|
-
const owned = new Set(
|
|
6064
|
+
const owned = new Set(manifest.keys);
|
|
5791
6065
|
const lineAt = (node) => {
|
|
5792
6066
|
const range = isNode(node) ? node.range : void 0;
|
|
5793
6067
|
return range ? lc.linePos(range[0]).line : void 0;
|
|
@@ -5800,13 +6074,13 @@ function parseManifest(sidecar, text, file, configDir, join7, target, entries) {
|
|
|
5800
6074
|
if (seen.has(spelled)) {
|
|
5801
6075
|
const first = seen.get(spelled);
|
|
5802
6076
|
throw new DocmetaError(
|
|
5803
|
-
`
|
|
6077
|
+
`Manifest ${where}: "${spelled}" is named twice (first at line ${first ?? "?"}). Merge the two entries into one.`
|
|
5804
6078
|
);
|
|
5805
6079
|
}
|
|
5806
6080
|
seen.set(spelled, entryLine);
|
|
5807
6081
|
if (!isMap3(pair.value)) {
|
|
5808
6082
|
throw new DocmetaError(
|
|
5809
|
-
`
|
|
6083
|
+
`Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
|
|
5810
6084
|
);
|
|
5811
6085
|
}
|
|
5812
6086
|
const indexKey = join7 === PATH_JOIN ? resolve5(configDir, spelled) : spelled;
|
|
@@ -5815,20 +6089,26 @@ function parseManifest(sidecar, text, file, configDir, join7, target, entries) {
|
|
|
5815
6089
|
const key = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
|
|
5816
6090
|
if (key === FILE_SCHEMA_KEY) {
|
|
5817
6091
|
throw new DocmetaError(
|
|
5818
|
-
`
|
|
6092
|
+
`Manifest ${where}: "${spelled}" sets "${FILE_SCHEMA_KEY}" \u2014 a manifest never chooses the schema a document is judged by; use "overrides" in the config.`
|
|
5819
6093
|
);
|
|
5820
6094
|
}
|
|
5821
6095
|
if (!owned.has(key)) {
|
|
5822
6096
|
throw new DocmetaError(
|
|
5823
|
-
`
|
|
6097
|
+
`Manifest ${where}: "${spelled}" sets "${key}", which this manifest does not own. Add it to the manifest's "keys", or remove it from the entry.`
|
|
5824
6098
|
);
|
|
5825
6099
|
}
|
|
5826
6100
|
const value = kv.value === null ? null : kv.value.toJS(doc, { maxAliasCount: 100 });
|
|
5827
6101
|
const line = lineAt(kv.key);
|
|
5828
|
-
values.set(key, {
|
|
6102
|
+
values.set(key, {
|
|
6103
|
+
value,
|
|
6104
|
+
collection,
|
|
6105
|
+
file,
|
|
6106
|
+
...line === void 0 ? {} : { line }
|
|
6107
|
+
});
|
|
5829
6108
|
}
|
|
5830
6109
|
target.set(indexKey, values);
|
|
5831
6110
|
entries.push({
|
|
6111
|
+
collection,
|
|
5832
6112
|
join: join7,
|
|
5833
6113
|
...join7 === PATH_JOIN ? { abs: indexKey } : {},
|
|
5834
6114
|
spelled,
|
|
@@ -5843,13 +6123,19 @@ function joinValue(raw) {
|
|
|
5843
6123
|
if (typeof raw === "boolean") return String(raw);
|
|
5844
6124
|
return void 0;
|
|
5845
6125
|
}
|
|
5846
|
-
function
|
|
6126
|
+
function mergeExternalMetadata(label, extracted, index, memberOf2, base) {
|
|
5847
6127
|
const none = () => void 0;
|
|
5848
|
-
if (!index
|
|
6128
|
+
if (!index || memberOf2.length === 0) {
|
|
6129
|
+
return { extracted, collisions: [], joins: [], locate: none };
|
|
6130
|
+
}
|
|
6131
|
+
const mine = (collection) => memberOf2.includes(collection);
|
|
5849
6132
|
const collisions = [];
|
|
5850
6133
|
for (const key of Object.keys(extracted.data)) {
|
|
5851
|
-
const
|
|
5852
|
-
|
|
6134
|
+
for (const owner of index.owners.get(key) ?? []) {
|
|
6135
|
+
if (mine(owner.collection)) {
|
|
6136
|
+
collisions.push({ key, file: owner.file, collection: owner.collection });
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
5853
6139
|
}
|
|
5854
6140
|
const sources = [];
|
|
5855
6141
|
const joins = [];
|
|
@@ -5863,8 +6149,23 @@ function mergeSidecars(label, extracted, index, base) {
|
|
|
5863
6149
|
const hit = byValue.get(value);
|
|
5864
6150
|
if (!hit) continue;
|
|
5865
6151
|
sources.push(hit);
|
|
5866
|
-
const first = hit.values().
|
|
5867
|
-
|
|
6152
|
+
const first = [...hit.values()].find((sv) => mine(sv.collection));
|
|
6153
|
+
if (first) {
|
|
6154
|
+
joins.push({ field, value, file: first.file, collection: first.collection });
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
for (const supplied of sources) {
|
|
6158
|
+
for (const key of supplied.keys()) {
|
|
6159
|
+
const owners = (index.owners.get(key) ?? []).filter(
|
|
6160
|
+
(o) => mine(o.collection)
|
|
6161
|
+
);
|
|
6162
|
+
const [a, b] = owners;
|
|
6163
|
+
if (a && b) {
|
|
6164
|
+
throw new DocmetaError(
|
|
6165
|
+
`${label}: "${key}" is owned by manifests in two of its collections, ${a.collection} (${a.file}) and ${b.collection} (${b.file}); a key has one manifest per file. Narrow one collection's paths or exclude.`
|
|
6166
|
+
);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
5868
6169
|
}
|
|
5869
6170
|
if (sources.length === 0) {
|
|
5870
6171
|
return { extracted, collisions, joins, locate: none };
|
|
@@ -5873,6 +6174,7 @@ function mergeSidecars(label, extracted, index, base) {
|
|
|
5873
6174
|
const merged = /* @__PURE__ */ new Map();
|
|
5874
6175
|
for (const supplied of sources) {
|
|
5875
6176
|
for (const [key, sv] of supplied) {
|
|
6177
|
+
if (!mine(sv.collection)) continue;
|
|
5876
6178
|
if (key in extracted.data) continue;
|
|
5877
6179
|
data[key] = sv.value;
|
|
5878
6180
|
merged.set(key, sv);
|
|
@@ -5893,20 +6195,23 @@ function topLevelKey(pointer) {
|
|
|
5893
6195
|
const seg = pointer.slice(1).split("/")[0] ?? "";
|
|
5894
6196
|
return seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
5895
6197
|
}
|
|
5896
|
-
function
|
|
6198
|
+
function externalMetadataPointer(key) {
|
|
5897
6199
|
return `/${escapePointerSegment(key)}`;
|
|
5898
6200
|
}
|
|
5899
|
-
function orphanEntries(index, loaded, base) {
|
|
6201
|
+
function orphanEntries(index, loaded, base, collections) {
|
|
5900
6202
|
if (!index) return [];
|
|
5901
6203
|
const have = new Set(loaded.map((l) => resolve5(base, l)));
|
|
5902
6204
|
return index.entries.filter(
|
|
5903
|
-
(e) => e.join === PATH_JOIN && e.abs !== void 0 && !have.has(e.abs)
|
|
6205
|
+
(e) => inCollections(e, collections) && e.join === PATH_JOIN && e.abs !== void 0 && !have.has(e.abs)
|
|
5904
6206
|
);
|
|
5905
6207
|
}
|
|
5906
|
-
function
|
|
6208
|
+
function inCollections(entry, collections) {
|
|
6209
|
+
return collections === void 0 || collections.includes(entry.collection);
|
|
6210
|
+
}
|
|
6211
|
+
function orphanJoins(index, matched, collections) {
|
|
5907
6212
|
if (!index) return [];
|
|
5908
6213
|
return index.entries.filter(
|
|
5909
|
-
(e) => e.join !== PATH_JOIN && !(matched.get(e.join)?.has(e.spelled) ?? false)
|
|
6214
|
+
(e) => inCollections(e, collections) && e.join !== PATH_JOIN && !(matched.get(e.join)?.has(e.spelled) ?? false)
|
|
5910
6215
|
);
|
|
5911
6216
|
}
|
|
5912
6217
|
function orphanError(orphans) {
|
|
@@ -5916,7 +6221,7 @@ function orphanError(orphans) {
|
|
|
5916
6221
|
const more = orphans.length > 1 ? ` (${orphans.length - 1} more)` : "";
|
|
5917
6222
|
const what = first.join === PATH_JOIN ? `names "${first.spelled}", which this run did not load` : `names ${first.join} "${first.spelled}", which no loaded document carries`;
|
|
5918
6223
|
return new DocmetaError(
|
|
5919
|
-
`
|
|
6224
|
+
`Manifest ${where} ${what}${more}. Fix the entry, or remove it.`
|
|
5920
6225
|
);
|
|
5921
6226
|
}
|
|
5922
6227
|
|
|
@@ -5961,7 +6266,7 @@ function parseMapping(text, source, opts) {
|
|
|
5961
6266
|
try {
|
|
5962
6267
|
raw = parseYaml(text);
|
|
5963
6268
|
} catch (err) {
|
|
5964
|
-
throw opts.toError(`${source}: invalid YAML: ${err
|
|
6269
|
+
throw opts.toError(`${source}: invalid YAML: ${errorMessage(err)}`);
|
|
5965
6270
|
}
|
|
5966
6271
|
if (raw == null) return null;
|
|
5967
6272
|
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -5969,13 +6274,22 @@ function parseMapping(text, source, opts) {
|
|
|
5969
6274
|
}
|
|
5970
6275
|
return raw;
|
|
5971
6276
|
}
|
|
6277
|
+
var COLLECTIONS_KEY = "collections";
|
|
5972
6278
|
function slice(document, opts) {
|
|
5973
6279
|
const { doc } = document;
|
|
5974
6280
|
if (doc !== null && Object.hasOwn(doc, opts.section)) {
|
|
5975
6281
|
return { value: doc[opts.section] ?? null, wrapped: true };
|
|
5976
6282
|
}
|
|
6283
|
+
if (doc !== null && Object.hasOwn(doc, COLLECTIONS_KEY)) {
|
|
6284
|
+
return { value: null, wrapped: true };
|
|
6285
|
+
}
|
|
5977
6286
|
return null;
|
|
5978
6287
|
}
|
|
6288
|
+
function collectionsOf(document, source, opts) {
|
|
6289
|
+
const { doc } = document;
|
|
6290
|
+
if (doc === null || !Object.hasOwn(doc, COLLECTIONS_KEY)) return [];
|
|
6291
|
+
return parseCollections(doc[COLLECTIONS_KEY], source, opts.toError);
|
|
6292
|
+
}
|
|
5979
6293
|
function relativeSource(cwd, path) {
|
|
5980
6294
|
return relative5(cwd, path).replace(/\\/g, "/");
|
|
5981
6295
|
}
|
|
@@ -5999,7 +6313,15 @@ async function findConfigFile(cwd, opts) {
|
|
|
5999
6313
|
`"${name2}" is the pre-rename name of the family config file. Rename it to "${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}".`
|
|
6000
6314
|
);
|
|
6001
6315
|
}
|
|
6002
|
-
return {
|
|
6316
|
+
return {
|
|
6317
|
+
path,
|
|
6318
|
+
dir,
|
|
6319
|
+
source,
|
|
6320
|
+
text: document.text,
|
|
6321
|
+
kind,
|
|
6322
|
+
collections: collectionsOf(document, source, opts),
|
|
6323
|
+
...found
|
|
6324
|
+
};
|
|
6003
6325
|
}
|
|
6004
6326
|
}
|
|
6005
6327
|
for (const name2 of opts.legacyNames) {
|
|
@@ -6008,7 +6330,7 @@ async function findConfigFile(cwd, opts) {
|
|
|
6008
6330
|
const document = await readDocument(path, source, opts);
|
|
6009
6331
|
if (document === null) continue;
|
|
6010
6332
|
warn(
|
|
6011
|
-
`"${name2}" is a deprecated config file name and will stop being read in a future major version. Move its keys under \`${opts.section}:\` in "${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}".`
|
|
6333
|
+
`"${name2}" is a deprecated config file name and will stop being read in a future major version. Move its keys under \`${opts.section}:\` in "${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}", and its paths, exclude and sidecars keys to a top-level collections: list, where sidecars becomes externalMetadata.`
|
|
6012
6334
|
);
|
|
6013
6335
|
return {
|
|
6014
6336
|
path,
|
|
@@ -6017,7 +6339,8 @@ async function findConfigFile(cwd, opts) {
|
|
|
6017
6339
|
text: document.text,
|
|
6018
6340
|
value: document.doc,
|
|
6019
6341
|
wrapped: false,
|
|
6020
|
-
kind: "legacy"
|
|
6342
|
+
kind: "legacy",
|
|
6343
|
+
collections: []
|
|
6021
6344
|
};
|
|
6022
6345
|
}
|
|
6023
6346
|
}
|
|
@@ -6039,6 +6362,7 @@ async function readConfigFile(explicitPath, cwd, opts) {
|
|
|
6039
6362
|
source: explicitPath,
|
|
6040
6363
|
text: document.text,
|
|
6041
6364
|
kind: "explicit",
|
|
6365
|
+
collections: collectionsOf(document, explicitPath, opts),
|
|
6042
6366
|
...found
|
|
6043
6367
|
};
|
|
6044
6368
|
}
|
|
@@ -6054,14 +6378,13 @@ function asElementPaths(value, where, source) {
|
|
|
6054
6378
|
try {
|
|
6055
6379
|
parseElementPath(path);
|
|
6056
6380
|
} catch (err) {
|
|
6057
|
-
throw new DocmetaError(`${source}: ${where} \u2014 ${err
|
|
6381
|
+
throw new DocmetaError(`${source}: ${where} \u2014 ${errorMessage(err)}`);
|
|
6058
6382
|
}
|
|
6059
6383
|
}
|
|
6060
6384
|
return list;
|
|
6061
6385
|
}
|
|
6062
|
-
var OVERRIDE_KEYS = ["
|
|
6386
|
+
var OVERRIDE_KEYS = ["collection", "files", "schemas", "elements"];
|
|
6063
6387
|
var CHECK_KEYS = ["name", "query"];
|
|
6064
|
-
var SIDECAR_KEYS = ["file", "keys", "tokenEnv", "join"];
|
|
6065
6388
|
var CHECK_NAME = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6066
6389
|
var RESERVED_CHECK_NAMES = /* @__PURE__ */ new Set(["query"]);
|
|
6067
6390
|
var FILL_KEYS = [
|
|
@@ -6073,12 +6396,9 @@ var FILL_KEYS = [
|
|
|
6073
6396
|
"concurrency"
|
|
6074
6397
|
];
|
|
6075
6398
|
var CONFIG_KEYS = [
|
|
6076
|
-
"paths",
|
|
6077
|
-
"exclude",
|
|
6078
6399
|
"schemas",
|
|
6079
6400
|
"overrides",
|
|
6080
6401
|
"checks",
|
|
6081
|
-
"sidecars",
|
|
6082
6402
|
"baseline",
|
|
6083
6403
|
"allowEmpty",
|
|
6084
6404
|
"respectGitignore",
|
|
@@ -6088,7 +6408,7 @@ var CONFIG_KEYS = [
|
|
|
6088
6408
|
"fill",
|
|
6089
6409
|
"elements"
|
|
6090
6410
|
];
|
|
6091
|
-
function
|
|
6411
|
+
function rejectUnknownKeys2(raw, allowed, where, source) {
|
|
6092
6412
|
for (const key of Object.keys(raw)) {
|
|
6093
6413
|
if (!allowed.includes(key)) {
|
|
6094
6414
|
throw new DocmetaError(
|
|
@@ -6158,7 +6478,7 @@ function parseSchemaEntry(entry, where, source) {
|
|
|
6158
6478
|
);
|
|
6159
6479
|
}
|
|
6160
6480
|
const raw = entry;
|
|
6161
|
-
|
|
6481
|
+
rejectUnknownKeys2(raw, SCHEMA_ENTRY_KEYS, where, source);
|
|
6162
6482
|
if (typeof raw.ref !== "string" || raw.ref.trim() === "") {
|
|
6163
6483
|
throw new DocmetaError(
|
|
6164
6484
|
`${source}: ${where}.ref must be a non-empty schema reference string.`
|
|
@@ -6195,12 +6515,13 @@ function parseConfig(text, source) {
|
|
|
6195
6515
|
raw = parseYaml2(text);
|
|
6196
6516
|
} catch (err) {
|
|
6197
6517
|
throw new DocmetaError(
|
|
6198
|
-
`${source}: invalid YAML: ${err
|
|
6518
|
+
`${source}: invalid YAML: ${errorMessage(err)}`
|
|
6199
6519
|
);
|
|
6200
6520
|
}
|
|
6201
6521
|
return parseConfigValue(raw, source);
|
|
6202
6522
|
}
|
|
6203
6523
|
function parseConfigValue(raw, source, section) {
|
|
6524
|
+
assertNoMovedKeys(raw, source);
|
|
6204
6525
|
if (section === void 0) return parseConfigDocument(raw, source);
|
|
6205
6526
|
try {
|
|
6206
6527
|
return parseConfigDocument(raw, source);
|
|
@@ -6222,92 +6543,56 @@ function withSection(message, source, section) {
|
|
|
6222
6543
|
if (/^[a-zA-Z]+(\[|\.| —)/.test(rest)) return `${head}${section}.${rest}`;
|
|
6223
6544
|
return message;
|
|
6224
6545
|
}
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
}
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6546
|
+
var CONFIG_REF = "https://hawkeyexl.github.io/manni/meta/reference/configuration/";
|
|
6547
|
+
var MOVED_KEYS = [
|
|
6548
|
+
[
|
|
6549
|
+
"paths",
|
|
6550
|
+
`Document sets are declared once for every tool, under a top-level collections: list. See ${CONFIG_REF}#collections`
|
|
6551
|
+
],
|
|
6552
|
+
[
|
|
6553
|
+
"exclude",
|
|
6554
|
+
`Document sets are declared once for every tool, under a top-level collections: list. See ${CONFIG_REF}#collections`
|
|
6555
|
+
],
|
|
6556
|
+
[
|
|
6557
|
+
"sidecars",
|
|
6558
|
+
`It is externalMetadata on a collection, under the top-level collections: list. See ${CONFIG_REF}#external-metadata`
|
|
6559
|
+
]
|
|
6560
|
+
];
|
|
6561
|
+
function assertNoMovedKeys(raw, source) {
|
|
6562
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return;
|
|
6563
|
+
const obj = raw;
|
|
6564
|
+
for (const [key, remedy] of MOVED_KEYS) {
|
|
6565
|
+
if (Object.hasOwn(obj, key)) {
|
|
6237
6566
|
throw new DocmetaError(
|
|
6238
|
-
`${source}:
|
|
6567
|
+
`${source}: "${key}" is no longer a meta key. ${remedy}`
|
|
6239
6568
|
);
|
|
6240
6569
|
}
|
|
6241
|
-
|
|
6570
|
+
}
|
|
6571
|
+
if (!Array.isArray(obj.overrides)) return;
|
|
6572
|
+
for (const [i, entry] of obj.overrides.entries()) {
|
|
6573
|
+
if (typeof entry === "object" && entry !== null && !Array.isArray(entry) && Object.hasOwn(entry, "name")) {
|
|
6242
6574
|
throw new DocmetaError(
|
|
6243
|
-
`${source}:
|
|
6575
|
+
`${source}: overrides[${i}] no longer carries "name". Define a collection with that name and point the override at it with collection:.`
|
|
6244
6576
|
);
|
|
6245
6577
|
}
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
}
|
|
6265
|
-
const keys = e.keys;
|
|
6266
|
-
if (e.join !== void 0) {
|
|
6267
|
-
if (typeof e.join !== "string" || e.join.trim() === "") {
|
|
6268
|
-
throw new DocmetaError(
|
|
6269
|
-
`${source}: sidecars[${i}].join must be "path" or the name of a top-level frontmatter field.`
|
|
6270
|
-
);
|
|
6271
|
-
}
|
|
6272
|
-
if (e.join === FILE_SCHEMA_KEY) {
|
|
6273
|
-
throw new DocmetaError(
|
|
6274
|
-
`${source}: sidecars[${i}].join may not be "${FILE_SCHEMA_KEY}".`
|
|
6275
|
-
);
|
|
6276
|
-
}
|
|
6277
|
-
if (keys.includes(e.join)) {
|
|
6278
|
-
throw new DocmetaError(
|
|
6279
|
-
`${source}: sidecars[${i}].join names "${e.join}", which the same entry owns \u2014 the value that selects an entry cannot come from the entry.`
|
|
6280
|
-
);
|
|
6281
|
-
}
|
|
6282
|
-
}
|
|
6283
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6284
|
-
for (const key of keys) {
|
|
6285
|
-
if (key === FILE_SCHEMA_KEY) {
|
|
6286
|
-
throw new DocmetaError(
|
|
6287
|
-
`${source}: sidecars[${i}].keys may not include "${FILE_SCHEMA_KEY}" \u2014 a sidecar never chooses the schema a document is judged by; use "overrides".`
|
|
6288
|
-
);
|
|
6289
|
-
}
|
|
6290
|
-
if (seen.has(key)) {
|
|
6291
|
-
throw new DocmetaError(
|
|
6292
|
-
`${source}: sidecars[${i}].keys lists "${key}" twice.`
|
|
6293
|
-
);
|
|
6294
|
-
}
|
|
6295
|
-
seen.add(key);
|
|
6296
|
-
const owner = owners.get(key);
|
|
6297
|
-
if (owner !== void 0) {
|
|
6298
|
-
throw new DocmetaError(
|
|
6299
|
-
`${source}: sidecars[${i}].keys claims "${key}", which sidecars[${owner}] already owns \u2014 a key has exactly one sidecar.`
|
|
6300
|
-
);
|
|
6301
|
-
}
|
|
6302
|
-
owners.set(key, i);
|
|
6303
|
-
}
|
|
6304
|
-
return {
|
|
6305
|
-
file: e.file,
|
|
6306
|
-
keys,
|
|
6307
|
-
...typeof e.tokenEnv === "string" ? { tokenEnv: e.tokenEnv } : {},
|
|
6308
|
-
...typeof e.join === "string" ? { join: e.join } : {}
|
|
6309
|
-
};
|
|
6310
|
-
});
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
function inSection(message, source, section) {
|
|
6581
|
+
return section === void 0 ? message : withSection(message, source, section);
|
|
6582
|
+
}
|
|
6583
|
+
function assertOverrideCollections(config, collections, source, section) {
|
|
6584
|
+
const names = new Set(collections.map((c) => c.name));
|
|
6585
|
+
for (const [i, ov] of (config.overrides ?? []).entries()) {
|
|
6586
|
+
if (ov.collection === void 0 || names.has(ov.collection)) continue;
|
|
6587
|
+
const defined = collections.length === 0 ? "(none)" : collections.map((c) => c.name).join(", ");
|
|
6588
|
+
throw new DocmetaError(
|
|
6589
|
+
inSection(
|
|
6590
|
+
`${source}: overrides[${i}].collection names "${ov.collection}", which collections: does not define. Defined: ${defined}.`,
|
|
6591
|
+
source,
|
|
6592
|
+
section
|
|
6593
|
+
)
|
|
6594
|
+
);
|
|
6595
|
+
}
|
|
6311
6596
|
}
|
|
6312
6597
|
function parseConfigDocument(raw, source) {
|
|
6313
6598
|
if (raw == null) return {};
|
|
@@ -6315,76 +6600,55 @@ function parseConfigDocument(raw, source) {
|
|
|
6315
6600
|
throw new DocmetaError(`${source}: top level must be a mapping.`);
|
|
6316
6601
|
}
|
|
6317
6602
|
const obj = raw;
|
|
6318
|
-
|
|
6603
|
+
rejectUnknownKeys2(obj, CONFIG_KEYS, "the top level", source);
|
|
6319
6604
|
const config = {};
|
|
6320
|
-
if (obj.paths !== void 0) config.paths = asStringList(obj.paths, "paths", source);
|
|
6321
|
-
if (obj.exclude !== void 0)
|
|
6322
|
-
config.exclude = asStringList(obj.exclude, "exclude", source);
|
|
6323
6605
|
if (obj.schemas !== void 0)
|
|
6324
6606
|
config.schemas = asSchemaList(obj.schemas, "schemas", source);
|
|
6325
6607
|
if (obj.overrides !== void 0) {
|
|
6326
6608
|
if (!Array.isArray(obj.overrides)) {
|
|
6327
6609
|
throw new DocmetaError(`${source}: "overrides" must be a list.`);
|
|
6328
6610
|
}
|
|
6329
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
6330
6611
|
config.overrides = obj.overrides.map((entry, i) => {
|
|
6331
6612
|
if (typeof entry !== "object" || entry === null) {
|
|
6332
6613
|
throw new DocmetaError(`${source}: overrides[${i}] must be a mapping.`);
|
|
6333
6614
|
}
|
|
6334
6615
|
const e = entry;
|
|
6335
|
-
|
|
6336
|
-
const
|
|
6337
|
-
const
|
|
6338
|
-
|
|
6339
|
-
if (schemas.length === 0 && (elements === void 0 || elements.length === 0)) {
|
|
6616
|
+
rejectUnknownKeys2(e, OVERRIDE_KEYS, `overrides[${i}]`, source);
|
|
6617
|
+
const hasFiles = e.files !== void 0;
|
|
6618
|
+
const hasCollection = e.collection !== void 0;
|
|
6619
|
+
if (hasFiles === hasCollection) {
|
|
6340
6620
|
throw new DocmetaError(
|
|
6341
|
-
`${source}: overrides[${i}]
|
|
6621
|
+
`${source}: overrides[${i}] must carry exactly one of "files" or "collection".`
|
|
6342
6622
|
);
|
|
6343
6623
|
}
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6624
|
+
const files = hasFiles ? asFileGlobs(e.files, `overrides[${i}].files`, source) : void 0;
|
|
6625
|
+
let collection;
|
|
6626
|
+
if (hasCollection) {
|
|
6627
|
+
if (typeof e.collection !== "string" || e.collection.trim() === "") {
|
|
6347
6628
|
throw new DocmetaError(
|
|
6348
|
-
`${source}: overrides[${i}].
|
|
6629
|
+
`${source}: overrides[${i}].collection must be a non-empty string naming a collection.`
|
|
6349
6630
|
);
|
|
6350
6631
|
}
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
);
|
|
6360
|
-
}
|
|
6361
|
-
if (schemas.length === 0) {
|
|
6362
|
-
throw new DocmetaError(
|
|
6363
|
-
`${source}: overrides[${i}] names a collection ("${e.name}") but sets no "schemas", so it can never win schema resolution and its view would always be empty. Add "schemas", or drop the name.`
|
|
6364
|
-
);
|
|
6365
|
-
}
|
|
6366
|
-
if (seenNames.has(e.name.toLowerCase())) {
|
|
6367
|
-
throw new DocmetaError(
|
|
6368
|
-
`${source}: overrides[${i}] reuses the name "${e.name}"; collection names must be unique.`
|
|
6369
|
-
);
|
|
6370
|
-
}
|
|
6371
|
-
seenNames.add(e.name.toLowerCase());
|
|
6372
|
-
name2 = e.name;
|
|
6632
|
+
collection = e.collection;
|
|
6633
|
+
}
|
|
6634
|
+
const schemas = e.schemas === void 0 ? [] : asStringList(e.schemas, `overrides[${i}].schemas`, source);
|
|
6635
|
+
const elements = e.elements === void 0 ? void 0 : asElementPaths(e.elements, `overrides[${i}].elements`, source);
|
|
6636
|
+
if (schemas.length === 0 && (elements === void 0 || elements.length === 0)) {
|
|
6637
|
+
throw new DocmetaError(
|
|
6638
|
+
`${source}: overrides[${i}] sets neither "schemas" nor "elements", so it has no effect.`
|
|
6639
|
+
);
|
|
6373
6640
|
}
|
|
6374
6641
|
return {
|
|
6375
|
-
files,
|
|
6642
|
+
...files !== void 0 ? { files } : {},
|
|
6643
|
+
...collection !== void 0 ? { collection } : {},
|
|
6376
6644
|
schemas,
|
|
6377
|
-
...elements ? { elements } : {}
|
|
6378
|
-
...name2 !== void 0 ? { name: name2 } : {}
|
|
6645
|
+
...elements ? { elements } : {}
|
|
6379
6646
|
};
|
|
6380
6647
|
});
|
|
6381
6648
|
}
|
|
6382
6649
|
if (obj.checks !== void 0) {
|
|
6383
6650
|
config.checks = parseChecks(obj.checks, source);
|
|
6384
6651
|
}
|
|
6385
|
-
if (obj.sidecars !== void 0) {
|
|
6386
|
-
config.sidecars = parseSidecars(obj.sidecars, source);
|
|
6387
|
-
}
|
|
6388
6652
|
if (obj.elements !== void 0) {
|
|
6389
6653
|
config.elements = asElementPaths(obj.elements, "elements", source);
|
|
6390
6654
|
}
|
|
@@ -6435,7 +6699,7 @@ function parseChecks(value, source) {
|
|
|
6435
6699
|
);
|
|
6436
6700
|
}
|
|
6437
6701
|
const e = entry;
|
|
6438
|
-
|
|
6702
|
+
rejectUnknownKeys2(e, CHECK_KEYS, `checks[${i}]`, source);
|
|
6439
6703
|
if (typeof e.name !== "string" || e.name === "") {
|
|
6440
6704
|
throw new DocmetaError(
|
|
6441
6705
|
`${source}: checks[${i}].name must be a non-empty string.`
|
|
@@ -6470,7 +6734,7 @@ function parseSchemaCache(value, source) {
|
|
|
6470
6734
|
throw new DocmetaError(`${source}: "schemaCache" must be a mapping.`);
|
|
6471
6735
|
}
|
|
6472
6736
|
const raw = value;
|
|
6473
|
-
|
|
6737
|
+
rejectUnknownKeys2(raw, SCHEMA_CACHE_KEYS, '"schemaCache"', source);
|
|
6474
6738
|
const schemaCache = {};
|
|
6475
6739
|
const ttl = raw.ttlHours;
|
|
6476
6740
|
if (ttl !== void 0) {
|
|
@@ -6490,7 +6754,7 @@ function parseSchemaTrust(value, source) {
|
|
|
6490
6754
|
);
|
|
6491
6755
|
}
|
|
6492
6756
|
const raw = value;
|
|
6493
|
-
|
|
6757
|
+
rejectUnknownKeys2(raw, SCHEMA_TRUST_KEYS, '"schemaTrust"', source);
|
|
6494
6758
|
const schemaTrust = {};
|
|
6495
6759
|
const mode = raw.documentRefs;
|
|
6496
6760
|
if (mode !== void 0) {
|
|
@@ -6517,7 +6781,7 @@ function parseFill(value, source) {
|
|
|
6517
6781
|
throw new DocmetaError(`${source}: "fill" must be a mapping.`);
|
|
6518
6782
|
}
|
|
6519
6783
|
const raw = value;
|
|
6520
|
-
|
|
6784
|
+
rejectUnknownKeys2(raw, FILL_KEYS, '"fill"', source);
|
|
6521
6785
|
const fill = {};
|
|
6522
6786
|
const asString = (key) => {
|
|
6523
6787
|
const v = raw[key];
|
|
@@ -6560,29 +6824,318 @@ async function loadConfig(explicitPath, cwd = process.cwd()) {
|
|
|
6560
6824
|
const file = explicitPath ? await readConfigFile(explicitPath, cwd, CONFIG_FILE) : await findConfigFile(cwd, CONFIG_FILE);
|
|
6561
6825
|
if (file === null) return null;
|
|
6562
6826
|
const section = file.wrapped ? META_SECTION : void 0;
|
|
6827
|
+
const config = parseConfigValue(file.value, file.source, section);
|
|
6828
|
+
assertOverrideCollections(config, file.collections, file.source, section);
|
|
6563
6829
|
return {
|
|
6564
|
-
config
|
|
6830
|
+
config,
|
|
6565
6831
|
path: file.path,
|
|
6566
6832
|
dir: file.dir,
|
|
6833
|
+
source: file.source,
|
|
6834
|
+
collections: file.collections,
|
|
6567
6835
|
...section !== void 0 ? { section } : {}
|
|
6568
6836
|
};
|
|
6569
6837
|
}
|
|
6838
|
+
var STDIN_TOKEN_LITERAL = "-";
|
|
6570
6839
|
async function resolveRunConfig(opts) {
|
|
6571
6840
|
const cwd = opts.cwd ?? process.cwd();
|
|
6841
|
+
const wanted = opts.collections ?? [];
|
|
6842
|
+
if (wanted.length > 0) {
|
|
6843
|
+
const typed = opts.inputs.filter((i) => i !== STDIN_TOKEN_LITERAL);
|
|
6844
|
+
if (typed.length > 0) {
|
|
6845
|
+
throw new DocmetaError(
|
|
6846
|
+
"--collection selects a configured collection; it cannot be combined with paths."
|
|
6847
|
+
);
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6572
6850
|
const loaded = opts.noConfig ? null : await loadConfig(opts.configPath, cwd);
|
|
6851
|
+
if (wanted.length > 0 && loaded === null) {
|
|
6852
|
+
throw new DocmetaError("--collection needs a config file to select from.");
|
|
6853
|
+
}
|
|
6573
6854
|
if (loaded) opts.onConfigLoaded?.({ path: loaded.path, dir: loaded.dir });
|
|
6574
6855
|
const config = loaded ? rebaseConfigSchemaRefs(loaded.config, loaded.dir, cwd) : null;
|
|
6575
|
-
const
|
|
6576
|
-
|
|
6577
|
-
|
|
6856
|
+
const collections = loaded ? selectCollections(
|
|
6857
|
+
loaded.collections,
|
|
6858
|
+
opts.collections,
|
|
6859
|
+
loaded.source,
|
|
6860
|
+
(message) => new DocmetaError(message)
|
|
6861
|
+
) : [];
|
|
6862
|
+
const fromCollections = opts.inputs.length === 0;
|
|
6863
|
+
const inputs = fromCollections ? collections.flatMap((c) => c.paths) : opts.inputs;
|
|
6864
|
+
const base = fromCollections && inputs.length > 0 && loaded ? loaded.dir : cwd;
|
|
6578
6865
|
return {
|
|
6579
6866
|
config,
|
|
6580
6867
|
inputs,
|
|
6581
6868
|
base,
|
|
6582
|
-
|
|
6869
|
+
collections,
|
|
6870
|
+
declaredCollections: loaded ? [...loaded.collections] : [],
|
|
6871
|
+
fromCollections,
|
|
6872
|
+
...loaded ? {
|
|
6873
|
+
configDir: loaded.dir,
|
|
6874
|
+
configPath: loaded.path,
|
|
6875
|
+
...loaded.section === void 0 ? {} : { configSection: loaded.section }
|
|
6876
|
+
} : {}
|
|
6583
6877
|
};
|
|
6584
6878
|
}
|
|
6585
6879
|
|
|
6880
|
+
// src/meta/core/collections.ts
|
|
6881
|
+
import { isAbsolute as isAbsolute3, relative as relative6, resolve as resolvePath2, sep as sep3 } from "node:path";
|
|
6882
|
+
|
|
6883
|
+
// src/meta/core/projection.ts
|
|
6884
|
+
var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
|
|
6885
|
+
var RESERVED = new Set(SYSTEM_COLUMNS);
|
|
6886
|
+
function quoteIdent(name2) {
|
|
6887
|
+
return `"${name2.replaceAll('"', '""')}"`;
|
|
6888
|
+
}
|
|
6889
|
+
function bindValue(value) {
|
|
6890
|
+
if (value === void 0 || value === null) return null;
|
|
6891
|
+
switch (typeof value) {
|
|
6892
|
+
case "boolean":
|
|
6893
|
+
return value ? 1 : 0;
|
|
6894
|
+
case "number":
|
|
6895
|
+
return Number.isFinite(value) ? value : null;
|
|
6896
|
+
case "bigint":
|
|
6897
|
+
case "string":
|
|
6898
|
+
return value;
|
|
6899
|
+
default:
|
|
6900
|
+
return JSON.stringify(value);
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
function corpusDataColumns(entries, extraKeys = []) {
|
|
6904
|
+
const keys = /* @__PURE__ */ new Set();
|
|
6905
|
+
for (const { extracted } of entries) {
|
|
6906
|
+
for (const key of Object.keys(extracted.data)) {
|
|
6907
|
+
if (key !== "" && !RESERVED.has(key)) keys.add(key);
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
for (const key of extraKeys) {
|
|
6911
|
+
if (key !== "" && !RESERVED.has(key)) keys.add(key);
|
|
6912
|
+
}
|
|
6913
|
+
return [...keys].sort();
|
|
6914
|
+
}
|
|
6915
|
+
function createDocsTable(db, entries, dataColumns) {
|
|
6916
|
+
db.exec(
|
|
6917
|
+
`CREATE TABLE docs ("_path" TEXT PRIMARY KEY, "_format" TEXT, "_present" INTEGER, "_data" TEXT${dataColumns.map((c) => `, ${quoteIdent(c)}`).join("")})`
|
|
6918
|
+
);
|
|
6919
|
+
const insert = db.prepare(
|
|
6920
|
+
`INSERT INTO docs VALUES (${["?", "?", "?", "?", ...dataColumns.map(() => "?")].join(", ")})`
|
|
6921
|
+
);
|
|
6922
|
+
db.exec("BEGIN");
|
|
6923
|
+
let committed = false;
|
|
6924
|
+
try {
|
|
6925
|
+
for (const { label, extracted } of entries) {
|
|
6926
|
+
insert.run(
|
|
6927
|
+
label,
|
|
6928
|
+
extracted.format,
|
|
6929
|
+
extracted.present ? 1 : 0,
|
|
6930
|
+
JSON.stringify(extracted.data),
|
|
6931
|
+
...dataColumns.map((c) => bindValue(extracted.data[c]))
|
|
6932
|
+
);
|
|
6933
|
+
}
|
|
6934
|
+
db.exec("COMMIT");
|
|
6935
|
+
committed = true;
|
|
6936
|
+
} finally {
|
|
6937
|
+
if (!committed) db.exec("ROLLBACK");
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
function registerLineFor(db, entries) {
|
|
6941
|
+
const byLabel = new Map(entries.map((e) => [e.label, e.extracted]));
|
|
6942
|
+
db.function("lineFor", { deterministic: true }, (path, key) => {
|
|
6943
|
+
if (typeof path !== "string" || typeof key !== "string") return null;
|
|
6944
|
+
return byLabel.get(path)?.lineFor(key) ?? null;
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6947
|
+
function assertSingleStatement(sql) {
|
|
6948
|
+
const cut = topLevelSemicolon(sql);
|
|
6949
|
+
if (cut === -1) return;
|
|
6950
|
+
if (!isTrivia(sql.slice(cut + 1))) {
|
|
6951
|
+
throw new DocmetaError(
|
|
6952
|
+
"Run a single SQL statement per query; text after the first `;` would be silently ignored."
|
|
6953
|
+
);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
function startsStringOrIdent(sql, i) {
|
|
6957
|
+
const ch = sql[i];
|
|
6958
|
+
return ch === "'" || ch === '"' || ch === "`" || ch === "[";
|
|
6959
|
+
}
|
|
6960
|
+
function startsComment(sql, i) {
|
|
6961
|
+
const ch = sql[i];
|
|
6962
|
+
return ch === "-" && sql[i + 1] === "-" || ch === "/" && sql[i + 1] === "*";
|
|
6963
|
+
}
|
|
6964
|
+
function skipStringOrIdent(sql, i) {
|
|
6965
|
+
const quote = sql[i];
|
|
6966
|
+
if (quote === "[") {
|
|
6967
|
+
let j2 = i + 1;
|
|
6968
|
+
while (j2 < sql.length && sql[j2] !== "]") j2++;
|
|
6969
|
+
return j2 + 1;
|
|
6970
|
+
}
|
|
6971
|
+
let j = i + 1;
|
|
6972
|
+
while (j < sql.length) {
|
|
6973
|
+
if (sql[j] === quote) {
|
|
6974
|
+
if (sql[j + 1] === quote) {
|
|
6975
|
+
j += 2;
|
|
6976
|
+
continue;
|
|
6977
|
+
}
|
|
6978
|
+
return j + 1;
|
|
6979
|
+
}
|
|
6980
|
+
j++;
|
|
6981
|
+
}
|
|
6982
|
+
return j;
|
|
6983
|
+
}
|
|
6984
|
+
function skipComment(sql, i) {
|
|
6985
|
+
if (sql[i] === "-") {
|
|
6986
|
+
let j = i + 2;
|
|
6987
|
+
while (j < sql.length && sql[j] !== "\n") j++;
|
|
6988
|
+
return j < sql.length ? j + 1 : j;
|
|
6989
|
+
}
|
|
6990
|
+
const end = sql.indexOf("*/", i + 2);
|
|
6991
|
+
return end === -1 ? sql.length : end + 2;
|
|
6992
|
+
}
|
|
6993
|
+
function topLevelSemicolon(sql) {
|
|
6994
|
+
let i = 0;
|
|
6995
|
+
while (i < sql.length) {
|
|
6996
|
+
if (startsStringOrIdent(sql, i)) {
|
|
6997
|
+
i = skipStringOrIdent(sql, i);
|
|
6998
|
+
} else if (startsComment(sql, i)) {
|
|
6999
|
+
i = skipComment(sql, i);
|
|
7000
|
+
} else if (sql[i] === ";") {
|
|
7001
|
+
return i;
|
|
7002
|
+
} else {
|
|
7003
|
+
i++;
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
return -1;
|
|
7007
|
+
}
|
|
7008
|
+
var PARAM_NAME_HEAD = /^[A-Za-z_][A-Za-z0-9_]*/;
|
|
7009
|
+
function isParamName(name2) {
|
|
7010
|
+
return PARAM_NAME_HEAD.exec(name2)?.[0] === name2;
|
|
7011
|
+
}
|
|
7012
|
+
function collectNamedParameters(sql) {
|
|
7013
|
+
const tokens = [];
|
|
7014
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7015
|
+
const prefixOf = /* @__PURE__ */ new Map();
|
|
7016
|
+
let i = 0;
|
|
7017
|
+
while (i < sql.length) {
|
|
7018
|
+
const ch = sql[i];
|
|
7019
|
+
if (startsStringOrIdent(sql, i)) {
|
|
7020
|
+
i = skipStringOrIdent(sql, i);
|
|
7021
|
+
} else if (startsComment(sql, i)) {
|
|
7022
|
+
i = skipComment(sql, i);
|
|
7023
|
+
} else if (ch === "$" || ch === ":" || ch === "@") {
|
|
7024
|
+
if (ch === ":" && sql[i + 1] === ":") {
|
|
7025
|
+
i += 2;
|
|
7026
|
+
continue;
|
|
7027
|
+
}
|
|
7028
|
+
const m = PARAM_NAME_HEAD.exec(sql.slice(i + 1));
|
|
7029
|
+
if (!m) {
|
|
7030
|
+
i++;
|
|
7031
|
+
continue;
|
|
7032
|
+
}
|
|
7033
|
+
const token = ch + m[0];
|
|
7034
|
+
const priorPrefix = prefixOf.get(m[0]);
|
|
7035
|
+
if (priorPrefix !== void 0 && priorPrefix !== ch) {
|
|
7036
|
+
throw new DocmetaError(
|
|
7037
|
+
`The statement references both ${priorPrefix}${m[0]} and ${token} \u2014 one name under two prefixes, which the engine rejects as conflicting. Use one spelling throughout.`
|
|
7038
|
+
);
|
|
7039
|
+
}
|
|
7040
|
+
prefixOf.set(m[0], ch);
|
|
7041
|
+
if (!seen.has(token)) {
|
|
7042
|
+
seen.add(token);
|
|
7043
|
+
tokens.push(token);
|
|
7044
|
+
}
|
|
7045
|
+
i += 1 + m[0].length;
|
|
7046
|
+
} else {
|
|
7047
|
+
i++;
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
return tokens;
|
|
7051
|
+
}
|
|
7052
|
+
function stripLeadingTrivia(sql) {
|
|
7053
|
+
let i = 0;
|
|
7054
|
+
for (; ; ) {
|
|
7055
|
+
while (i < sql.length && /\s/.test(sql[i] ?? "")) i++;
|
|
7056
|
+
if (startsComment(sql, i)) {
|
|
7057
|
+
i = skipComment(sql, i);
|
|
7058
|
+
continue;
|
|
7059
|
+
}
|
|
7060
|
+
return sql.slice(i);
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
function isTrivia(text) {
|
|
7064
|
+
let i = 0;
|
|
7065
|
+
while (i < text.length) {
|
|
7066
|
+
const ch = text[i];
|
|
7067
|
+
if (ch === void 0 || /\s/.test(ch) || ch === ";") {
|
|
7068
|
+
i++;
|
|
7069
|
+
continue;
|
|
7070
|
+
}
|
|
7071
|
+
if (startsComment(text, i)) {
|
|
7072
|
+
i = skipComment(text, i);
|
|
7073
|
+
continue;
|
|
7074
|
+
}
|
|
7075
|
+
return false;
|
|
7076
|
+
}
|
|
7077
|
+
return true;
|
|
7078
|
+
}
|
|
7079
|
+
var sqliteModule;
|
|
7080
|
+
function loadSqlite() {
|
|
7081
|
+
sqliteModule ??= (async () => {
|
|
7082
|
+
const original = process.emitWarning.bind(process);
|
|
7083
|
+
const filtered = (warning, ...rest) => {
|
|
7084
|
+
const text = typeof warning === "string" ? warning : warning.message;
|
|
7085
|
+
if (text.includes("SQLite is an experimental feature")) return;
|
|
7086
|
+
Reflect.apply(original, process, [warning, ...rest]);
|
|
7087
|
+
};
|
|
7088
|
+
process.emitWarning = filtered;
|
|
7089
|
+
try {
|
|
7090
|
+
return await import("node:sqlite");
|
|
7091
|
+
} finally {
|
|
7092
|
+
process.emitWarning = original;
|
|
7093
|
+
}
|
|
7094
|
+
})();
|
|
7095
|
+
return sqliteModule;
|
|
7096
|
+
}
|
|
7097
|
+
|
|
7098
|
+
// src/meta/core/collections.ts
|
|
7099
|
+
function memberOf(collections, configDir, base, label) {
|
|
7100
|
+
if (collections.length === 0) return [];
|
|
7101
|
+
if (label === STDIN_LABEL) return [];
|
|
7102
|
+
const rel = relative6(configDir, resolvePath2(base, label)).split(sep3).join("/");
|
|
7103
|
+
if (isAbsolute3(rel)) return [];
|
|
7104
|
+
return collections.filter((c) => isMember(c, rel)).map((c) => c.name);
|
|
7105
|
+
}
|
|
7106
|
+
function retainMembers(files, membersFor) {
|
|
7107
|
+
return files.filter((label) => membersFor(label).length > 0);
|
|
7108
|
+
}
|
|
7109
|
+
function collectionNames(collections) {
|
|
7110
|
+
return (collections ?? []).map((c) => c.name);
|
|
7111
|
+
}
|
|
7112
|
+
function collectCollections(entries, params) {
|
|
7113
|
+
const declared = params.collections ?? [];
|
|
7114
|
+
if (declared.length === 0) return [];
|
|
7115
|
+
const base = params.base ?? ".";
|
|
7116
|
+
const configDir = params.configDir ?? base;
|
|
7117
|
+
const members = /* @__PURE__ */ new Map();
|
|
7118
|
+
for (const entry of entries) {
|
|
7119
|
+
for (const name2 of memberOf(declared, configDir, base, entry.label)) {
|
|
7120
|
+
const list = members.get(name2);
|
|
7121
|
+
if (list) list.push(entry.label);
|
|
7122
|
+
else members.set(name2, [entry.label]);
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
return declared.map((c) => ({ name: c.name, members: members.get(c.name) ?? [] }));
|
|
7126
|
+
}
|
|
7127
|
+
function quoteSqlString(value) {
|
|
7128
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
7129
|
+
}
|
|
7130
|
+
function createCollectionViews(db, collections) {
|
|
7131
|
+
for (const c of collections) {
|
|
7132
|
+
const where = c.members.length === 0 ? "0" : `_path IN (${c.members.map(quoteSqlString).join(", ")})`;
|
|
7133
|
+
db.exec(
|
|
7134
|
+
`CREATE VIEW ${quoteIdent(c.name)} AS SELECT * FROM docs WHERE ${where}`
|
|
7135
|
+
);
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
|
|
6586
7139
|
// src/meta/core/validator.ts
|
|
6587
7140
|
import * as AjvDraft07Ns from "ajv";
|
|
6588
7141
|
import * as Ajv2019Ns from "ajv/dist/2019.js";
|
|
@@ -6721,7 +7274,7 @@ var Validator = class {
|
|
|
6721
7274
|
return ajv.compile(schema);
|
|
6722
7275
|
} catch (err) {
|
|
6723
7276
|
throw new DocmetaError(
|
|
6724
|
-
`Schema "${ref}" failed to compile: ${err
|
|
7277
|
+
`Schema "${ref}" failed to compile: ${errorMessage(err)}`
|
|
6725
7278
|
);
|
|
6726
7279
|
}
|
|
6727
7280
|
}
|
|
@@ -6813,7 +7366,7 @@ import { resolve as resolve9, extname as extname2 } from "node:path";
|
|
|
6813
7366
|
// src/meta/core/baseline.ts
|
|
6814
7367
|
import { createHash as createHash3 } from "node:crypto";
|
|
6815
7368
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
6816
|
-
import { relative as
|
|
7369
|
+
import { relative as relative7, resolve as resolve8 } from "node:path";
|
|
6817
7370
|
var DEFAULT_BASELINE_PATH = ".manni-baseline.json";
|
|
6818
7371
|
var LEGACY_BASELINE_PATH = ".docmeta-baseline.json";
|
|
6819
7372
|
var BASELINE_VERSION = 1;
|
|
@@ -6821,13 +7374,13 @@ var FINGERPRINT_RE = /^[0-9a-f]{16}$/;
|
|
|
6821
7374
|
function canonicalFilePath(file, ctx) {
|
|
6822
7375
|
if (!ctx) return file;
|
|
6823
7376
|
const from = ctx.runBase ?? ctx.base;
|
|
6824
|
-
return
|
|
7377
|
+
return relative7(ctx.base, resolve8(from, file)).replace(/\\/g, "/");
|
|
6825
7378
|
}
|
|
6826
7379
|
function canonicalSchemaRef(ref, ctx) {
|
|
6827
7380
|
if (!ctx) return ref;
|
|
6828
7381
|
if (classifyRef(ref).kind !== "file") return ref;
|
|
6829
7382
|
const abs = resolve8(ctx.cwd, ref);
|
|
6830
|
-
return
|
|
7383
|
+
return relative7(ctx.base, abs).replace(/\\/g, "/");
|
|
6831
7384
|
}
|
|
6832
7385
|
function fingerprint(e, ctx) {
|
|
6833
7386
|
const parts = [
|
|
@@ -6850,409 +7403,135 @@ function serializeBaseline(baseline) {
|
|
|
6850
7403
|
entries
|
|
6851
7404
|
},
|
|
6852
7405
|
null,
|
|
6853
|
-
2
|
|
6854
|
-
)}
|
|
6855
|
-
`;
|
|
6856
|
-
}
|
|
6857
|
-
function bad(source, detail) {
|
|
6858
|
-
throw new DocmetaError(`Baseline "${source}": ${detail}`);
|
|
6859
|
-
}
|
|
6860
|
-
function parseBaseline(text, source) {
|
|
6861
|
-
let raw;
|
|
6862
|
-
try {
|
|
6863
|
-
raw = JSON.parse(stripBom(text));
|
|
6864
|
-
} catch (err) {
|
|
6865
|
-
bad(source, `invalid JSON: ${err.message}`);
|
|
6866
|
-
}
|
|
6867
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
6868
|
-
bad(source, "top level must be an object.");
|
|
6869
|
-
}
|
|
6870
|
-
const obj = raw;
|
|
6871
|
-
if (obj.version !== BASELINE_VERSION) {
|
|
6872
|
-
bad(
|
|
6873
|
-
source,
|
|
6874
|
-
`unsupported version ${JSON.stringify(obj.version)} (this manni writes version ${BASELINE_VERSION}). Re-record it with \`manni meta validate --write-baseline\`.`
|
|
6875
|
-
);
|
|
6876
|
-
}
|
|
6877
|
-
const generatedWith = typeof obj.generatedWith === "string" ? obj.generatedWith : "";
|
|
6878
|
-
const rawEntries = obj.entries;
|
|
6879
|
-
if (typeof rawEntries !== "object" || rawEntries === null || Array.isArray(rawEntries)) {
|
|
6880
|
-
bad(source, '"entries" must be an object mapping file paths to fingerprints.');
|
|
6881
|
-
}
|
|
6882
|
-
const entries = /* @__PURE__ */ Object.create(null);
|
|
6883
|
-
for (const [file, value] of Object.entries(
|
|
6884
|
-
rawEntries
|
|
6885
|
-
)) {
|
|
6886
|
-
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
6887
|
-
bad(source, `entries["${file}"] must be a list of fingerprint strings.`);
|
|
6888
|
-
}
|
|
6889
|
-
const malformed = value.find((v) => !FINGERPRINT_RE.test(v));
|
|
6890
|
-
if (malformed !== void 0) {
|
|
6891
|
-
bad(
|
|
6892
|
-
source,
|
|
6893
|
-
`entries["${file}"] contains ${JSON.stringify(malformed)}, which is not a fingerprint (16 lowercase hex characters).`
|
|
6894
|
-
);
|
|
6895
|
-
}
|
|
6896
|
-
entries[file] = value;
|
|
6897
|
-
}
|
|
6898
|
-
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
6899
|
-
}
|
|
6900
|
-
async function readBaseline(absPath, source) {
|
|
6901
|
-
let text;
|
|
6902
|
-
try {
|
|
6903
|
-
text = await readFile5(absPath, "utf8");
|
|
6904
|
-
} catch (err) {
|
|
6905
|
-
if (err.code === "ENOENT") return null;
|
|
6906
|
-
throw new DocmetaError(
|
|
6907
|
-
`Baseline "${source}" could not be read: ${err.message}`
|
|
6908
|
-
);
|
|
6909
|
-
}
|
|
6910
|
-
return parseBaseline(text, source);
|
|
6911
|
-
}
|
|
6912
|
-
async function writeBaselineFile(absPath, baseline, source) {
|
|
6913
|
-
try {
|
|
6914
|
-
await writeFileAtomic(absPath, serializeBaseline(baseline));
|
|
6915
|
-
} catch (err) {
|
|
6916
|
-
throw new DocmetaError(
|
|
6917
|
-
`Baseline "${source}" could not be written: ${err.message}`
|
|
6918
|
-
);
|
|
6919
|
-
}
|
|
6920
|
-
}
|
|
6921
|
-
function buildBaseline(results, generatedWith, ctx) {
|
|
6922
|
-
const entries = /* @__PURE__ */ Object.create(null);
|
|
6923
|
-
for (const r of results) {
|
|
6924
|
-
if (r.errors.length === 0) continue;
|
|
6925
|
-
const prints = [...new Set(r.errors.map((e) => fingerprint(e, ctx)))].sort();
|
|
6926
|
-
entries[canonicalFilePath(r.file, ctx)] = prints;
|
|
6927
|
-
}
|
|
6928
|
-
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
6929
|
-
}
|
|
6930
|
-
function applyBaseline(results, baseline, ctx) {
|
|
6931
|
-
let recorded = 0;
|
|
6932
|
-
let suppressed = 0;
|
|
6933
|
-
let stale = 0;
|
|
6934
|
-
const applied = results.map((r) => {
|
|
6935
|
-
const known = baseline.entries[canonicalFilePath(r.file, ctx)];
|
|
6936
|
-
if (!known) return r;
|
|
6937
|
-
const recordedHere = new Set(known);
|
|
6938
|
-
recorded += recordedHere.size;
|
|
6939
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6940
|
-
const fresh = [];
|
|
6941
|
-
for (const e of r.errors) {
|
|
6942
|
-
const print = fingerprint(e, ctx);
|
|
6943
|
-
if (recordedHere.has(print)) {
|
|
6944
|
-
seen.add(print);
|
|
6945
|
-
suppressed += 1;
|
|
6946
|
-
} else {
|
|
6947
|
-
fresh.push(e);
|
|
6948
|
-
}
|
|
6949
|
-
}
|
|
6950
|
-
stale += recordedHere.size - seen.size;
|
|
6951
|
-
const baselined = r.errors.length - fresh.length;
|
|
6952
|
-
return {
|
|
6953
|
-
...r,
|
|
6954
|
-
ok: fresh.length === 0,
|
|
6955
|
-
errors: fresh,
|
|
6956
|
-
...baselined > 0 ? { baselined } : {}
|
|
6957
|
-
};
|
|
6958
|
-
});
|
|
6959
|
-
return { results: applied, recorded, suppressed, stale };
|
|
6960
|
-
}
|
|
6961
|
-
function diffBaselines(previous, next) {
|
|
6962
|
-
const flatten = (b) => {
|
|
6963
|
-
const out = /* @__PURE__ */ new Set();
|
|
6964
|
-
if (!b) return out;
|
|
6965
|
-
for (const [file, prints] of Object.entries(b.entries)) {
|
|
6966
|
-
for (const p of prints) out.add(`${file}\0${p}`);
|
|
6967
|
-
}
|
|
6968
|
-
return out;
|
|
6969
|
-
};
|
|
6970
|
-
const before = flatten(previous);
|
|
6971
|
-
const after = flatten(next);
|
|
6972
|
-
let added = 0;
|
|
6973
|
-
let removed = 0;
|
|
6974
|
-
for (const k of after) if (!before.has(k)) added += 1;
|
|
6975
|
-
for (const k of before) if (!after.has(k)) removed += 1;
|
|
6976
|
-
return { added, removed };
|
|
6977
|
-
}
|
|
6978
|
-
function countFingerprints(baseline) {
|
|
6979
|
-
let n = 0;
|
|
6980
|
-
for (const prints of Object.values(baseline.entries)) n += prints.length;
|
|
6981
|
-
return n;
|
|
6982
|
-
}
|
|
6983
|
-
|
|
6984
|
-
// src/meta/core/projection.ts
|
|
6985
|
-
var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
|
|
6986
|
-
var RESERVED = new Set(SYSTEM_COLUMNS);
|
|
6987
|
-
function quoteIdent(name2) {
|
|
6988
|
-
return `"${name2.replaceAll('"', '""')}"`;
|
|
6989
|
-
}
|
|
6990
|
-
function bindValue(value) {
|
|
6991
|
-
if (value === void 0 || value === null) return null;
|
|
6992
|
-
switch (typeof value) {
|
|
6993
|
-
case "boolean":
|
|
6994
|
-
return value ? 1 : 0;
|
|
6995
|
-
case "number":
|
|
6996
|
-
return Number.isFinite(value) ? value : null;
|
|
6997
|
-
case "bigint":
|
|
6998
|
-
case "string":
|
|
6999
|
-
return value;
|
|
7000
|
-
default:
|
|
7001
|
-
return JSON.stringify(value);
|
|
7002
|
-
}
|
|
7003
|
-
}
|
|
7004
|
-
function corpusDataColumns(entries, extraKeys = []) {
|
|
7005
|
-
const keys = /* @__PURE__ */ new Set();
|
|
7006
|
-
for (const { extracted } of entries) {
|
|
7007
|
-
for (const key of Object.keys(extracted.data)) {
|
|
7008
|
-
if (key !== "" && !RESERVED.has(key)) keys.add(key);
|
|
7009
|
-
}
|
|
7010
|
-
}
|
|
7011
|
-
for (const key of extraKeys) {
|
|
7012
|
-
if (key !== "" && !RESERVED.has(key)) keys.add(key);
|
|
7013
|
-
}
|
|
7014
|
-
return [...keys].sort();
|
|
7015
|
-
}
|
|
7016
|
-
function createDocsTable(db, entries, dataColumns) {
|
|
7017
|
-
db.exec(
|
|
7018
|
-
`CREATE TABLE docs ("_path" TEXT PRIMARY KEY, "_format" TEXT, "_present" INTEGER, "_data" TEXT${dataColumns.map((c) => `, ${quoteIdent(c)}`).join("")})`
|
|
7019
|
-
);
|
|
7020
|
-
const insert = db.prepare(
|
|
7021
|
-
`INSERT INTO docs VALUES (${["?", "?", "?", "?", ...dataColumns.map(() => "?")].join(", ")})`
|
|
7022
|
-
);
|
|
7023
|
-
db.exec("BEGIN");
|
|
7024
|
-
for (const { label, extracted } of entries) {
|
|
7025
|
-
insert.run(
|
|
7026
|
-
label,
|
|
7027
|
-
extracted.format,
|
|
7028
|
-
extracted.present ? 1 : 0,
|
|
7029
|
-
JSON.stringify(extracted.data),
|
|
7030
|
-
...dataColumns.map((c) => bindValue(extracted.data[c]))
|
|
7031
|
-
);
|
|
7032
|
-
}
|
|
7033
|
-
db.exec("COMMIT");
|
|
7034
|
-
}
|
|
7035
|
-
function registerLineFor(db, entries) {
|
|
7036
|
-
const byLabel = new Map(entries.map((e) => [e.label, e.extracted]));
|
|
7037
|
-
db.function("lineFor", { deterministic: true }, (path, key) => {
|
|
7038
|
-
if (typeof path !== "string" || typeof key !== "string") return null;
|
|
7039
|
-
return byLabel.get(path)?.lineFor(key) ?? null;
|
|
7040
|
-
});
|
|
7041
|
-
}
|
|
7042
|
-
function assertSingleStatement(sql) {
|
|
7043
|
-
const cut = topLevelSemicolon(sql);
|
|
7044
|
-
if (cut === -1) return;
|
|
7045
|
-
if (!isTrivia(sql.slice(cut + 1))) {
|
|
7046
|
-
throw new DocmetaError(
|
|
7047
|
-
"Run a single SQL statement per query; text after the first `;` would be silently ignored."
|
|
7048
|
-
);
|
|
7049
|
-
}
|
|
7050
|
-
}
|
|
7051
|
-
function startsStringOrIdent(sql, i) {
|
|
7052
|
-
const ch = sql[i];
|
|
7053
|
-
return ch === "'" || ch === '"' || ch === "`" || ch === "[";
|
|
7054
|
-
}
|
|
7055
|
-
function startsComment(sql, i) {
|
|
7056
|
-
const ch = sql[i];
|
|
7057
|
-
return ch === "-" && sql[i + 1] === "-" || ch === "/" && sql[i + 1] === "*";
|
|
7058
|
-
}
|
|
7059
|
-
function skipStringOrIdent(sql, i) {
|
|
7060
|
-
const quote = sql[i];
|
|
7061
|
-
if (quote === "[") {
|
|
7062
|
-
let j2 = i + 1;
|
|
7063
|
-
while (j2 < sql.length && sql[j2] !== "]") j2++;
|
|
7064
|
-
return j2 + 1;
|
|
7065
|
-
}
|
|
7066
|
-
let j = i + 1;
|
|
7067
|
-
while (j < sql.length) {
|
|
7068
|
-
if (sql[j] === quote) {
|
|
7069
|
-
if (sql[j + 1] === quote) {
|
|
7070
|
-
j += 2;
|
|
7071
|
-
continue;
|
|
7072
|
-
}
|
|
7073
|
-
return j + 1;
|
|
7074
|
-
}
|
|
7075
|
-
j++;
|
|
7076
|
-
}
|
|
7077
|
-
return j;
|
|
7078
|
-
}
|
|
7079
|
-
function skipComment(sql, i) {
|
|
7080
|
-
if (sql[i] === "-") {
|
|
7081
|
-
let j = i + 2;
|
|
7082
|
-
while (j < sql.length && sql[j] !== "\n") j++;
|
|
7083
|
-
return j < sql.length ? j + 1 : j;
|
|
7084
|
-
}
|
|
7085
|
-
const end = sql.indexOf("*/", i + 2);
|
|
7086
|
-
return end === -1 ? sql.length : end + 2;
|
|
7087
|
-
}
|
|
7088
|
-
function topLevelSemicolon(sql) {
|
|
7089
|
-
let i = 0;
|
|
7090
|
-
while (i < sql.length) {
|
|
7091
|
-
if (startsStringOrIdent(sql, i)) {
|
|
7092
|
-
i = skipStringOrIdent(sql, i);
|
|
7093
|
-
} else if (startsComment(sql, i)) {
|
|
7094
|
-
i = skipComment(sql, i);
|
|
7095
|
-
} else if (sql[i] === ";") {
|
|
7096
|
-
return i;
|
|
7097
|
-
} else {
|
|
7098
|
-
i++;
|
|
7099
|
-
}
|
|
7100
|
-
}
|
|
7101
|
-
return -1;
|
|
7406
|
+
2
|
|
7407
|
+
)}
|
|
7408
|
+
`;
|
|
7102
7409
|
}
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
return PARAM_NAME_HEAD.exec(name2)?.[0] === name2;
|
|
7410
|
+
function bad(source, detail) {
|
|
7411
|
+
throw new DocmetaError(`Baseline "${source}": ${detail}`);
|
|
7106
7412
|
}
|
|
7107
|
-
function
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
const ch = sql[i];
|
|
7114
|
-
if (startsStringOrIdent(sql, i)) {
|
|
7115
|
-
i = skipStringOrIdent(sql, i);
|
|
7116
|
-
} else if (startsComment(sql, i)) {
|
|
7117
|
-
i = skipComment(sql, i);
|
|
7118
|
-
} else if (ch === "$" || ch === ":" || ch === "@") {
|
|
7119
|
-
if (ch === ":" && sql[i + 1] === ":") {
|
|
7120
|
-
i += 2;
|
|
7121
|
-
continue;
|
|
7122
|
-
}
|
|
7123
|
-
const m = PARAM_NAME_HEAD.exec(sql.slice(i + 1));
|
|
7124
|
-
if (!m) {
|
|
7125
|
-
i++;
|
|
7126
|
-
continue;
|
|
7127
|
-
}
|
|
7128
|
-
const token = ch + m[0];
|
|
7129
|
-
const priorPrefix = prefixOf.get(m[0]);
|
|
7130
|
-
if (priorPrefix !== void 0 && priorPrefix !== ch) {
|
|
7131
|
-
throw new DocmetaError(
|
|
7132
|
-
`The statement references both ${priorPrefix}${m[0]} and ${token} \u2014 one name under two prefixes, which the engine rejects as conflicting. Use one spelling throughout.`
|
|
7133
|
-
);
|
|
7134
|
-
}
|
|
7135
|
-
prefixOf.set(m[0], ch);
|
|
7136
|
-
if (!seen.has(token)) {
|
|
7137
|
-
seen.add(token);
|
|
7138
|
-
tokens.push(token);
|
|
7139
|
-
}
|
|
7140
|
-
i += 1 + m[0].length;
|
|
7141
|
-
} else {
|
|
7142
|
-
i++;
|
|
7143
|
-
}
|
|
7413
|
+
function parseBaseline(text, source) {
|
|
7414
|
+
let raw;
|
|
7415
|
+
try {
|
|
7416
|
+
raw = JSON.parse(stripBom(text));
|
|
7417
|
+
} catch (err) {
|
|
7418
|
+
bad(source, `invalid JSON: ${errorMessage(err)}`);
|
|
7144
7419
|
}
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
function stripLeadingTrivia(sql) {
|
|
7148
|
-
let i = 0;
|
|
7149
|
-
for (; ; ) {
|
|
7150
|
-
while (i < sql.length && /\s/.test(sql[i] ?? "")) i++;
|
|
7151
|
-
if (startsComment(sql, i)) {
|
|
7152
|
-
i = skipComment(sql, i);
|
|
7153
|
-
continue;
|
|
7154
|
-
}
|
|
7155
|
-
return sql.slice(i);
|
|
7420
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
7421
|
+
bad(source, "top level must be an object.");
|
|
7156
7422
|
}
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7423
|
+
const obj = raw;
|
|
7424
|
+
if (obj.version !== BASELINE_VERSION) {
|
|
7425
|
+
bad(
|
|
7426
|
+
source,
|
|
7427
|
+
`unsupported version ${JSON.stringify(obj.version)} (this manni writes version ${BASELINE_VERSION}). Re-record it with \`manni meta validate --write-baseline\`.`
|
|
7428
|
+
);
|
|
7429
|
+
}
|
|
7430
|
+
const generatedWith = typeof obj.generatedWith === "string" ? obj.generatedWith : "";
|
|
7431
|
+
const rawEntries = obj.entries;
|
|
7432
|
+
if (typeof rawEntries !== "object" || rawEntries === null || Array.isArray(rawEntries)) {
|
|
7433
|
+
bad(source, '"entries" must be an object mapping file paths to fingerprints.');
|
|
7434
|
+
}
|
|
7435
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
7436
|
+
for (const [file, value] of Object.entries(
|
|
7437
|
+
rawEntries
|
|
7438
|
+
)) {
|
|
7439
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
7440
|
+
bad(source, `entries["${file}"] must be a list of fingerprint strings.`);
|
|
7165
7441
|
}
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7442
|
+
const malformed = value.find((v) => !FINGERPRINT_RE.test(v));
|
|
7443
|
+
if (malformed !== void 0) {
|
|
7444
|
+
bad(
|
|
7445
|
+
source,
|
|
7446
|
+
`entries["${file}"] contains ${JSON.stringify(malformed)}, which is not a fingerprint (16 lowercase hex characters).`
|
|
7447
|
+
);
|
|
7169
7448
|
}
|
|
7170
|
-
|
|
7449
|
+
entries[file] = value;
|
|
7171
7450
|
}
|
|
7172
|
-
return
|
|
7451
|
+
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
7173
7452
|
}
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
return await import("node:sqlite");
|
|
7186
|
-
} finally {
|
|
7187
|
-
process.emitWarning = original;
|
|
7188
|
-
}
|
|
7189
|
-
})();
|
|
7190
|
-
return sqliteModule;
|
|
7453
|
+
async function readBaseline(absPath, source) {
|
|
7454
|
+
let text;
|
|
7455
|
+
try {
|
|
7456
|
+
text = await readFile5(absPath, "utf8");
|
|
7457
|
+
} catch (err) {
|
|
7458
|
+
if (err.code === "ENOENT") return null;
|
|
7459
|
+
throw new DocmetaError(
|
|
7460
|
+
`Baseline "${source}" could not be read: ${errorMessage(err)}`
|
|
7461
|
+
);
|
|
7462
|
+
}
|
|
7463
|
+
return parseBaseline(text, source);
|
|
7191
7464
|
}
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7465
|
+
async function writeBaselineFile(absPath, baseline, source) {
|
|
7466
|
+
try {
|
|
7467
|
+
await writeFileAtomic(absPath, serializeBaseline(baseline));
|
|
7468
|
+
} catch (err) {
|
|
7469
|
+
throw new DocmetaError(
|
|
7470
|
+
`Baseline "${source}" could not be written: ${errorMessage(err)}`
|
|
7471
|
+
);
|
|
7472
|
+
}
|
|
7198
7473
|
}
|
|
7199
|
-
function
|
|
7200
|
-
|
|
7474
|
+
function buildBaseline(results, generatedWith, ctx) {
|
|
7475
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
7476
|
+
for (const r of results) {
|
|
7477
|
+
if (r.errors.length === 0) continue;
|
|
7478
|
+
const prints = [...new Set(r.errors.map((e) => fingerprint(e, ctx)))].sort();
|
|
7479
|
+
entries[canonicalFilePath(r.file, ctx)] = prints;
|
|
7480
|
+
}
|
|
7481
|
+
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
7201
7482
|
}
|
|
7202
|
-
function
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
if (
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
...params.trustRoot ? { trustRoot: params.trustRoot } : {}
|
|
7221
|
-
});
|
|
7222
|
-
} catch {
|
|
7223
|
-
continue;
|
|
7224
|
-
}
|
|
7225
|
-
}
|
|
7226
|
-
if (resolved.source === "override" && resolved.overrideIndex !== void 0) {
|
|
7227
|
-
const list = members.get(resolved.overrideIndex);
|
|
7228
|
-
if (list) list.push(entry.label);
|
|
7229
|
-
else members.set(resolved.overrideIndex, [entry.label]);
|
|
7230
|
-
continue;
|
|
7231
|
-
}
|
|
7232
|
-
if (resolved.source === "document") {
|
|
7233
|
-
const excluded = named.find((g) => matchesFileGlob(g.files, entry.label));
|
|
7234
|
-
if (excluded) {
|
|
7235
|
-
params.onNotice?.(
|
|
7236
|
-
`${entry.label}: not in the "${excluded.name}" collection \u2014 its own "${FILE_SCHEMA_KEY}" won schema resolution over the override (${overrideGlobs(excluded.files).join(", ")}). Membership follows the schema a file is validated as; set schemaTrust.documentRefs to "none" to let the override decide.`
|
|
7237
|
-
);
|
|
7483
|
+
function applyBaseline(results, baseline, ctx) {
|
|
7484
|
+
let recorded = 0;
|
|
7485
|
+
let suppressed = 0;
|
|
7486
|
+
let stale = 0;
|
|
7487
|
+
const applied = results.map((r) => {
|
|
7488
|
+
const known = baseline.entries[canonicalFilePath(r.file, ctx)];
|
|
7489
|
+
if (!known) return r;
|
|
7490
|
+
const recordedHere = new Set(known);
|
|
7491
|
+
recorded += recordedHere.size;
|
|
7492
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7493
|
+
const fresh = [];
|
|
7494
|
+
for (const e of r.errors) {
|
|
7495
|
+
const print = fingerprint(e, ctx);
|
|
7496
|
+
if (recordedHere.has(print)) {
|
|
7497
|
+
seen.add(print);
|
|
7498
|
+
suppressed += 1;
|
|
7499
|
+
} else {
|
|
7500
|
+
fresh.push(e);
|
|
7238
7501
|
}
|
|
7239
7502
|
}
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7503
|
+
stale += recordedHere.size - seen.size;
|
|
7504
|
+
const baselined = r.errors.length - fresh.length;
|
|
7505
|
+
return {
|
|
7506
|
+
...r,
|
|
7507
|
+
ok: fresh.length === 0,
|
|
7508
|
+
errors: fresh,
|
|
7509
|
+
...baselined > 0 ? { baselined } : {}
|
|
7510
|
+
};
|
|
7511
|
+
});
|
|
7512
|
+
return { results: applied, recorded, suppressed, stale };
|
|
7245
7513
|
}
|
|
7246
|
-
function
|
|
7247
|
-
|
|
7514
|
+
function diffBaselines(previous, next) {
|
|
7515
|
+
const flatten = (b) => {
|
|
7516
|
+
const out = /* @__PURE__ */ new Set();
|
|
7517
|
+
if (!b) return out;
|
|
7518
|
+
for (const [file, prints] of Object.entries(b.entries)) {
|
|
7519
|
+
for (const p of prints) out.add(`${file}\0${p}`);
|
|
7520
|
+
}
|
|
7521
|
+
return out;
|
|
7522
|
+
};
|
|
7523
|
+
const before = flatten(previous);
|
|
7524
|
+
const after = flatten(next);
|
|
7525
|
+
let added = 0;
|
|
7526
|
+
let removed = 0;
|
|
7527
|
+
for (const k of after) if (!before.has(k)) added += 1;
|
|
7528
|
+
for (const k of before) if (!after.has(k)) removed += 1;
|
|
7529
|
+
return { added, removed };
|
|
7248
7530
|
}
|
|
7249
|
-
function
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
`CREATE VIEW ${quoteIdent(c.name)} AS SELECT * FROM docs WHERE ${where}`
|
|
7254
|
-
);
|
|
7255
|
-
}
|
|
7531
|
+
function countFingerprints(baseline) {
|
|
7532
|
+
let n = 0;
|
|
7533
|
+
for (const prints of Object.values(baseline.entries)) n += prints.length;
|
|
7534
|
+
return n;
|
|
7256
7535
|
}
|
|
7257
7536
|
|
|
7258
7537
|
// src/meta/core/checks.ts
|
|
@@ -7335,7 +7614,7 @@ async function runChecks(checks, entries, ctx = {}) {
|
|
|
7335
7614
|
tokens = collectNamedParameters(check.query);
|
|
7336
7615
|
} catch (err) {
|
|
7337
7616
|
throw new DocmetaError(
|
|
7338
|
-
`check "${check.name}": ${err
|
|
7617
|
+
`check "${check.name}": ${errorMessage(err)}`
|
|
7339
7618
|
);
|
|
7340
7619
|
}
|
|
7341
7620
|
if (tokens.length > 0) {
|
|
@@ -7351,7 +7630,7 @@ async function runChecks(checks, entries, ctx = {}) {
|
|
|
7351
7630
|
columns = stmt.columns().map((c) => c.name);
|
|
7352
7631
|
rows = stmt.all();
|
|
7353
7632
|
} catch (err) {
|
|
7354
|
-
const message = err
|
|
7633
|
+
const message = errorMessage(err);
|
|
7355
7634
|
throw new DocmetaError(
|
|
7356
7635
|
message.includes("attempt to write a readonly database") ? `check "${check.name}": ${message} \u2014 checks are read-only by design; a check is a SELECT over the projection. Edit the corpus with \`manni meta query\` instead.` : `check "${check.name}": ${message}`
|
|
7357
7636
|
);
|
|
@@ -7416,17 +7695,27 @@ function resolveBaselineRequest(opts, configured, configDir, cwd) {
|
|
|
7416
7695
|
}
|
|
7417
7696
|
async function runValidate(opts) {
|
|
7418
7697
|
const cwd = opts.cwd ?? process.cwd();
|
|
7419
|
-
const {
|
|
7698
|
+
const {
|
|
7699
|
+
config,
|
|
7700
|
+
inputs,
|
|
7701
|
+
base,
|
|
7702
|
+
configDir,
|
|
7703
|
+
collections,
|
|
7704
|
+
declaredCollections,
|
|
7705
|
+
fromCollections
|
|
7706
|
+
} = await resolveRunConfig({
|
|
7420
7707
|
cwd,
|
|
7421
7708
|
configPath: opts.configPath,
|
|
7422
7709
|
noConfig: opts.noConfig,
|
|
7423
7710
|
inputs: opts.inputs,
|
|
7711
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
7424
7712
|
onConfigLoaded: opts.onConfigLoaded
|
|
7425
7713
|
});
|
|
7714
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
7426
7715
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
7427
7716
|
if (inputs.length === 0) {
|
|
7428
7717
|
throw new DocmetaError(
|
|
7429
|
-
"No files to validate. Pass paths/globs, or
|
|
7718
|
+
"No files to validate. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
7430
7719
|
);
|
|
7431
7720
|
}
|
|
7432
7721
|
const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
|
|
@@ -7438,8 +7727,8 @@ async function runValidate(opts) {
|
|
|
7438
7727
|
const exts = opts.exts ?? (forcedExtractor ? forcedExtractor.extensions : void 0);
|
|
7439
7728
|
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
7440
7729
|
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
7441
|
-
const exclude =
|
|
7442
|
-
const { files, gitignoreSkipped } = await resolveTargetSet({
|
|
7730
|
+
const exclude = opts.exclude ?? [];
|
|
7731
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
7443
7732
|
inputs: fileInputs,
|
|
7444
7733
|
exts,
|
|
7445
7734
|
exclude,
|
|
@@ -7451,6 +7740,7 @@ async function runValidate(opts) {
|
|
|
7451
7740
|
onNotice: opts.onNotice
|
|
7452
7741
|
})
|
|
7453
7742
|
});
|
|
7743
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
7454
7744
|
assertNonEmpty({
|
|
7455
7745
|
files,
|
|
7456
7746
|
inputs: fileInputs,
|
|
@@ -7462,7 +7752,7 @@ async function runValidate(opts) {
|
|
|
7462
7752
|
action: "validated"
|
|
7463
7753
|
});
|
|
7464
7754
|
const trustRoot = schemaTrustRoot(cwd, configDir);
|
|
7465
|
-
const
|
|
7755
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
7466
7756
|
configDir: configDir ?? cwd,
|
|
7467
7757
|
base,
|
|
7468
7758
|
offline: opts.offline ?? config?.offline ?? false
|
|
@@ -7483,16 +7773,18 @@ async function runValidate(opts) {
|
|
|
7483
7773
|
);
|
|
7484
7774
|
const results = [];
|
|
7485
7775
|
const joinHits = /* @__PURE__ */ new Map();
|
|
7486
|
-
const
|
|
7776
|
+
const scopedToCollections = (opts.collections?.length ?? 0) > 0;
|
|
7777
|
+
const scopedByFlags = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
|
|
7487
7778
|
usingStdin || opts.as !== void 0 || opts.exts !== void 0 || opts.exclude !== void 0 && opts.exclude.length > 0 || opts.respectGitignore !== void 0 || (opts.cliSchemas?.length ?? 0) > 0;
|
|
7779
|
+
const scoped = scopedByFlags || scopedToCollections;
|
|
7488
7780
|
const configuredChecks = config?.checks ?? [];
|
|
7489
|
-
if (!
|
|
7490
|
-
const
|
|
7781
|
+
if (!scopedByFlags) {
|
|
7782
|
+
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
7783
|
+
const orphans = orphanEntries(externalMetadata, files, base, covered);
|
|
7491
7784
|
if (orphans.length > 0) throw orphanError(orphans);
|
|
7492
7785
|
}
|
|
7493
7786
|
const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
|
|
7494
7787
|
const checkEntries = [];
|
|
7495
|
-
const checkResolved = /* @__PURE__ */ new Map();
|
|
7496
7788
|
const processOne = async (label, content, extension) => {
|
|
7497
7789
|
const extractor = forcedExtractor ?? extractorForExtension(extension);
|
|
7498
7790
|
if (!extractor) {
|
|
@@ -7500,19 +7792,20 @@ async function runValidate(opts) {
|
|
|
7500
7792
|
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
7501
7793
|
);
|
|
7502
7794
|
}
|
|
7795
|
+
const members = membersFor(label);
|
|
7503
7796
|
let extracted;
|
|
7504
7797
|
try {
|
|
7505
7798
|
extracted = extractor.extract(content, label, {
|
|
7506
|
-
elements: resolveElements(label, config)
|
|
7799
|
+
elements: resolveElements(label, config, members)
|
|
7507
7800
|
});
|
|
7508
7801
|
} catch (err) {
|
|
7509
7802
|
if (err instanceof DocmetaError) throw err;
|
|
7510
7803
|
results.push(
|
|
7511
|
-
parseErrorResult(label, extractor.name, err
|
|
7804
|
+
parseErrorResult(label, extractor.name, errorMessage(err), "parse")
|
|
7512
7805
|
);
|
|
7513
7806
|
return;
|
|
7514
7807
|
}
|
|
7515
|
-
const merged =
|
|
7808
|
+
const merged = mergeExternalMetadata(label, extracted, externalMetadata, members, base);
|
|
7516
7809
|
extracted = merged.extracted;
|
|
7517
7810
|
for (const j of merged.joins) {
|
|
7518
7811
|
const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
|
|
@@ -7530,6 +7823,7 @@ async function runValidate(opts) {
|
|
|
7530
7823
|
fileSchema: extracted.data[FILE_SCHEMA_KEY],
|
|
7531
7824
|
cliSchemas: opts.cliSchemas,
|
|
7532
7825
|
config,
|
|
7826
|
+
memberOf: members,
|
|
7533
7827
|
// A document's own `$schema` is measured from the run's directory, the
|
|
7534
7828
|
// same base `loadSchema` will read it from, and contained to the
|
|
7535
7829
|
// repository the run is standing in.
|
|
@@ -7539,11 +7833,10 @@ async function runValidate(opts) {
|
|
|
7539
7833
|
});
|
|
7540
7834
|
} catch (err) {
|
|
7541
7835
|
results.push(
|
|
7542
|
-
parseErrorResult(label, extractor.name, err
|
|
7836
|
+
parseErrorResult(label, extractor.name, errorMessage(err), "schema")
|
|
7543
7837
|
);
|
|
7544
7838
|
return;
|
|
7545
7839
|
}
|
|
7546
|
-
if (checksWillRun) checkResolved.set(label, resolved);
|
|
7547
7840
|
const schemaSet = resolved.schemas;
|
|
7548
7841
|
let errors;
|
|
7549
7842
|
try {
|
|
@@ -7563,14 +7856,14 @@ async function runValidate(opts) {
|
|
|
7563
7856
|
);
|
|
7564
7857
|
return;
|
|
7565
7858
|
}
|
|
7566
|
-
for (const { key, file } of merged.collisions) {
|
|
7859
|
+
for (const { key, file, collection } of merged.collisions) {
|
|
7567
7860
|
const line = extracted.lineFor(key);
|
|
7568
7861
|
errors.push({
|
|
7569
|
-
schema:
|
|
7570
|
-
keyword:
|
|
7862
|
+
schema: EXTERNAL_OWNED_SCHEMA,
|
|
7863
|
+
keyword: EXTERNAL_KEYWORD,
|
|
7571
7864
|
subject: key,
|
|
7572
|
-
instancePath:
|
|
7573
|
-
message: `"${key}" is owned by
|
|
7865
|
+
instancePath: externalMetadataPointer(key),
|
|
7866
|
+
message: `"${key}" is owned by manifest ${file} (collection ${collection}); remove it from the document`,
|
|
7574
7867
|
...line != null ? { line } : {}
|
|
7575
7868
|
});
|
|
7576
7869
|
}
|
|
@@ -7595,7 +7888,7 @@ async function runValidate(opts) {
|
|
|
7595
7888
|
const content = await readFile6(resolve9(base, file), "utf8");
|
|
7596
7889
|
await processOne(file, content, extname2(file));
|
|
7597
7890
|
}
|
|
7598
|
-
if (
|
|
7891
|
+
if (externalMetadata) {
|
|
7599
7892
|
const byLabel = new Map(results.map((r) => [r.file, r]));
|
|
7600
7893
|
const matched = /* @__PURE__ */ new Map();
|
|
7601
7894
|
for (const [field, byValue] of joinHits) {
|
|
@@ -7607,10 +7900,10 @@ async function runValidate(opts) {
|
|
|
7607
7900
|
const result = byLabel.get(hit.label);
|
|
7608
7901
|
if (!result) continue;
|
|
7609
7902
|
result.errors.push({
|
|
7610
|
-
schema:
|
|
7611
|
-
keyword:
|
|
7903
|
+
schema: EXTERNAL_DUPLICATE_SCHEMA,
|
|
7904
|
+
keyword: EXTERNAL_KEYWORD,
|
|
7612
7905
|
subject: value,
|
|
7613
|
-
instancePath:
|
|
7906
|
+
instancePath: externalMetadataPointer(field),
|
|
7614
7907
|
message: `${hits.length} documents carry ${field} "${value}"; ${hit.file} cannot tell them apart (${others})`,
|
|
7615
7908
|
...hit.line != null ? { line: hit.line } : {}
|
|
7616
7909
|
});
|
|
@@ -7618,26 +7911,26 @@ async function runValidate(opts) {
|
|
|
7618
7911
|
}
|
|
7619
7912
|
}
|
|
7620
7913
|
}
|
|
7621
|
-
if (!
|
|
7622
|
-
const
|
|
7914
|
+
if (!scopedByFlags) {
|
|
7915
|
+
const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
|
|
7916
|
+
const orphans = orphanJoins(externalMetadata, matched, covered);
|
|
7623
7917
|
if (orphans.length > 0) throw orphanError(orphans);
|
|
7624
7918
|
}
|
|
7625
7919
|
}
|
|
7626
7920
|
if (configuredChecks.length > 0 && opts.checks !== false) {
|
|
7627
7921
|
if (scoped) {
|
|
7628
|
-
opts.onNotice?.(
|
|
7922
|
+
opts.onNotice?.(
|
|
7923
|
+
scopedToCollections ? `corpus checks skipped: run is scoped to collections ${collections.map((c) => c.name).join(", ")}` : "corpus checks skipped: run is scoped"
|
|
7924
|
+
);
|
|
7629
7925
|
} else {
|
|
7630
7926
|
const findings = await runChecks(configuredChecks, checkEntries, {
|
|
7631
|
-
//
|
|
7632
|
-
//
|
|
7633
|
-
//
|
|
7634
|
-
//
|
|
7635
|
-
|
|
7636
|
-
...
|
|
7637
|
-
|
|
7638
|
-
trustRoot,
|
|
7639
|
-
resolved: checkResolved,
|
|
7640
|
-
...opts.onNotice ? { onNotice: opts.onNotice } : {}
|
|
7927
|
+
// Every declared collection is a view (0041 rule 7), holding the
|
|
7928
|
+
// members this run loaded — decided by the same path arithmetic
|
|
7929
|
+
// `membersFor` uses, so a check and a `manni meta query` over the same
|
|
7930
|
+
// corpus see the same collections.
|
|
7931
|
+
collections: declaredCollections,
|
|
7932
|
+
...configDir !== void 0 ? { configDir } : {},
|
|
7933
|
+
base
|
|
7641
7934
|
});
|
|
7642
7935
|
const byFile = new Map(results.map((r) => [r.file, r]));
|
|
7643
7936
|
for (const [file, errs] of findings) {
|
|
@@ -7719,17 +8012,18 @@ async function runGet(opts) {
|
|
|
7719
8012
|
if (opts.fields.length === 0) {
|
|
7720
8013
|
throw new DocmetaError("Specify at least one field to get.");
|
|
7721
8014
|
}
|
|
7722
|
-
const { config, inputs, base, configDir } = await resolveRunConfig({
|
|
8015
|
+
const { config, inputs, base, configDir, collections, fromCollections } = await resolveRunConfig({
|
|
7723
8016
|
cwd,
|
|
7724
8017
|
configPath: opts.configPath,
|
|
7725
8018
|
noConfig: opts.noConfig,
|
|
7726
8019
|
inputs: opts.inputs,
|
|
8020
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
7727
8021
|
onConfigLoaded: opts.onConfigLoaded
|
|
7728
8022
|
});
|
|
7729
8023
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
7730
8024
|
if (inputs.length === 0) {
|
|
7731
8025
|
throw new DocmetaError(
|
|
7732
|
-
"No files to read. Pass paths/globs, or
|
|
8026
|
+
"No files to read. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
7733
8027
|
);
|
|
7734
8028
|
}
|
|
7735
8029
|
const forced = opts.as ? extractorByName(opts.as) : void 0;
|
|
@@ -7741,8 +8035,9 @@ async function runGet(opts) {
|
|
|
7741
8035
|
const exts = opts.exts ?? (forced ? forced.extensions : void 0);
|
|
7742
8036
|
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
7743
8037
|
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
7744
|
-
const exclude =
|
|
7745
|
-
const
|
|
8038
|
+
const exclude = opts.exclude ?? [];
|
|
8039
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
8040
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
7746
8041
|
inputs: fileInputs,
|
|
7747
8042
|
exts,
|
|
7748
8043
|
exclude,
|
|
@@ -7754,6 +8049,7 @@ async function runGet(opts) {
|
|
|
7754
8049
|
onNotice: opts.onNotice
|
|
7755
8050
|
})
|
|
7756
8051
|
});
|
|
8052
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
7757
8053
|
assertNonEmpty({
|
|
7758
8054
|
files,
|
|
7759
8055
|
inputs: fileInputs,
|
|
@@ -7765,7 +8061,7 @@ async function runGet(opts) {
|
|
|
7765
8061
|
action: "read"
|
|
7766
8062
|
});
|
|
7767
8063
|
const out = [];
|
|
7768
|
-
const
|
|
8064
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
7769
8065
|
configDir: configDir ?? cwd,
|
|
7770
8066
|
base,
|
|
7771
8067
|
offline: opts.offline ?? config?.offline ?? false
|
|
@@ -7777,14 +8073,16 @@ async function runGet(opts) {
|
|
|
7777
8073
|
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
7778
8074
|
);
|
|
7779
8075
|
}
|
|
8076
|
+
const members = membersFor(label);
|
|
7780
8077
|
let extracted;
|
|
7781
8078
|
try {
|
|
7782
|
-
extracted =
|
|
8079
|
+
extracted = mergeExternalMetadata(
|
|
7783
8080
|
label,
|
|
7784
8081
|
extractor.extract(content, label, {
|
|
7785
|
-
elements: resolveElements(label, config)
|
|
8082
|
+
elements: resolveElements(label, config, members)
|
|
7786
8083
|
}),
|
|
7787
|
-
|
|
8084
|
+
externalMetadata,
|
|
8085
|
+
members,
|
|
7788
8086
|
base
|
|
7789
8087
|
).extracted;
|
|
7790
8088
|
} catch (err) {
|
|
@@ -7844,7 +8142,7 @@ function parseJsonPointer(pointer) {
|
|
|
7844
8142
|
// src/meta/commands/schemas.ts
|
|
7845
8143
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7846
8144
|
import { mkdir as mkdir2, readFile as readFile8 } from "node:fs/promises";
|
|
7847
|
-
import { dirname as dirname5, extname as extname4, isAbsolute as
|
|
8145
|
+
import { dirname as dirname5, extname as extname4, isAbsolute as isAbsolute4, join as join5, relative as relative8, resolve as resolve11, sep as sep4 } from "node:path";
|
|
7848
8146
|
import { parseDocument as parseDocument4, parse as parseYaml3, stringify } from "yaml";
|
|
7849
8147
|
function getSchemasInfo() {
|
|
7850
8148
|
return { builtins: listBuiltins(), formats: listFormats() };
|
|
@@ -7852,7 +8150,7 @@ function getSchemasInfo() {
|
|
|
7852
8150
|
var DEFAULT_VENDOR_DIR = "./schema";
|
|
7853
8151
|
var DEFAULT_CONFIG_NAME = "manni.config.yaml";
|
|
7854
8152
|
function posixRelative(from, to) {
|
|
7855
|
-
return
|
|
8153
|
+
return relative8(from, to).split(sep4).join("/");
|
|
7856
8154
|
}
|
|
7857
8155
|
function vendorFileName(url) {
|
|
7858
8156
|
let parsed2;
|
|
@@ -7944,7 +8242,7 @@ async function runVendorSchema(opts) {
|
|
|
7944
8242
|
const absFile = join5(absDir, vendorFileName(url));
|
|
7945
8243
|
await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
|
|
7946
8244
|
const fromConfig = posixRelative(configDir, absFile);
|
|
7947
|
-
const ref =
|
|
8245
|
+
const ref = isAbsolute4(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
|
|
7948
8246
|
const { bytes } = await fetchSchemaBytes(url, {
|
|
7949
8247
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
7950
8248
|
...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
|
|
@@ -8088,22 +8386,23 @@ async function runInferSchema(opts) {
|
|
|
8088
8386
|
`--min-coverage must be a percentage between 0 and 100, got ${minCoverage}.`
|
|
8089
8387
|
);
|
|
8090
8388
|
}
|
|
8091
|
-
const { config, inputs, base, configDir } = await resolveRunConfig({
|
|
8389
|
+
const { config, inputs, base, configDir, collections, fromCollections } = await resolveRunConfig({
|
|
8092
8390
|
cwd,
|
|
8093
8391
|
configPath: opts.configPath,
|
|
8094
8392
|
noConfig: opts.noConfig,
|
|
8095
8393
|
inputs: opts.inputs,
|
|
8394
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
8096
8395
|
onConfigLoaded: opts.onConfigLoaded
|
|
8097
8396
|
});
|
|
8098
8397
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
8099
|
-
const
|
|
8398
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
8100
8399
|
configDir: configDir ?? cwd,
|
|
8101
8400
|
base,
|
|
8102
8401
|
offline: opts.offline ?? config?.offline ?? false
|
|
8103
8402
|
});
|
|
8104
8403
|
if (inputs.length === 0) {
|
|
8105
8404
|
throw new DocmetaError(
|
|
8106
|
-
"No files to scan. Pass paths/globs, or
|
|
8405
|
+
"No files to scan. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
8107
8406
|
);
|
|
8108
8407
|
}
|
|
8109
8408
|
const forced = opts.as ? extractorByName(opts.as) : void 0;
|
|
@@ -8136,8 +8435,9 @@ async function runInferSchema(opts) {
|
|
|
8136
8435
|
const exts = opts.exts ?? (forced ? forced.extensions : void 0);
|
|
8137
8436
|
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
8138
8437
|
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
8139
|
-
const exclude =
|
|
8140
|
-
const
|
|
8438
|
+
const exclude = opts.exclude ?? [];
|
|
8439
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
8440
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
8141
8441
|
inputs: fileInputs,
|
|
8142
8442
|
exts,
|
|
8143
8443
|
exclude,
|
|
@@ -8149,6 +8449,7 @@ async function runInferSchema(opts) {
|
|
|
8149
8449
|
onNotice: opts.onNotice
|
|
8150
8450
|
})
|
|
8151
8451
|
});
|
|
8452
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
8152
8453
|
assertNonEmpty({
|
|
8153
8454
|
files,
|
|
8154
8455
|
inputs: fileInputs,
|
|
@@ -8171,14 +8472,16 @@ async function runInferSchema(opts) {
|
|
|
8171
8472
|
);
|
|
8172
8473
|
}
|
|
8173
8474
|
filesScanned += 1;
|
|
8475
|
+
const members = membersFor(label);
|
|
8174
8476
|
let extracted;
|
|
8175
8477
|
try {
|
|
8176
|
-
extracted =
|
|
8478
|
+
extracted = mergeExternalMetadata(
|
|
8177
8479
|
label,
|
|
8178
8480
|
extractor.extract(content, label, {
|
|
8179
|
-
elements: resolveElements(label, config)
|
|
8481
|
+
elements: resolveElements(label, config, members)
|
|
8180
8482
|
}),
|
|
8181
|
-
|
|
8483
|
+
externalMetadata,
|
|
8484
|
+
members,
|
|
8182
8485
|
base
|
|
8183
8486
|
).extracted;
|
|
8184
8487
|
} catch (err) {
|
|
@@ -8264,7 +8567,7 @@ async function runInferSchema(opts) {
|
|
|
8264
8567
|
import { randomBytes } from "node:crypto";
|
|
8265
8568
|
import { existsSync as existsSync4 } from "node:fs";
|
|
8266
8569
|
import { mkdir as mkdir3, open, readFile as readFile9, rename as rename2, rm as rm2 } from "node:fs/promises";
|
|
8267
|
-
import { dirname as dirname6, isAbsolute as
|
|
8570
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve12, extname as extname5, sep as sep5 } from "node:path";
|
|
8268
8571
|
import { parseDocument as parseDocument5, isMap as isMap4, isSeq as isSeq2, isScalar as isScalar3 } from "yaml";
|
|
8269
8572
|
async function runQuery(opts) {
|
|
8270
8573
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -8297,19 +8600,29 @@ async function runQuery(opts) {
|
|
|
8297
8600
|
);
|
|
8298
8601
|
}
|
|
8299
8602
|
}
|
|
8300
|
-
const {
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8603
|
+
const {
|
|
8604
|
+
config,
|
|
8605
|
+
inputs,
|
|
8606
|
+
base,
|
|
8607
|
+
configDir,
|
|
8608
|
+
configPath,
|
|
8609
|
+
configSection,
|
|
8610
|
+
collections,
|
|
8611
|
+
declaredCollections,
|
|
8612
|
+
fromCollections
|
|
8613
|
+
} = await resolveRunConfig({
|
|
8614
|
+
cwd,
|
|
8615
|
+
configPath: opts.configPath,
|
|
8616
|
+
noConfig: opts.noConfig,
|
|
8617
|
+
inputs: opts.inputs,
|
|
8618
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
8619
|
+
onConfigLoaded: opts.onConfigLoaded
|
|
8620
|
+
});
|
|
8621
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
8309
8622
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
8310
8623
|
if (inputs.length === 0) {
|
|
8311
8624
|
throw new DocmetaError(
|
|
8312
|
-
"No files to read. Pass paths/globs, or
|
|
8625
|
+
"No files to read. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
8313
8626
|
);
|
|
8314
8627
|
}
|
|
8315
8628
|
const forced = opts.as ? extractorByName(opts.as) : void 0;
|
|
@@ -8321,8 +8634,8 @@ async function runQuery(opts) {
|
|
|
8321
8634
|
const exts = opts.exts ?? (forced ? forced.extensions : void 0);
|
|
8322
8635
|
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
8323
8636
|
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
8324
|
-
const exclude =
|
|
8325
|
-
const { files, gitignoreSkipped } = await resolveTargetSet({
|
|
8637
|
+
const exclude = opts.exclude ?? [];
|
|
8638
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
8326
8639
|
inputs: fileInputs,
|
|
8327
8640
|
exts,
|
|
8328
8641
|
exclude,
|
|
@@ -8334,6 +8647,7 @@ async function runQuery(opts) {
|
|
|
8334
8647
|
onNotice: opts.onNotice
|
|
8335
8648
|
})
|
|
8336
8649
|
});
|
|
8650
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
8337
8651
|
assertNonEmpty({
|
|
8338
8652
|
files,
|
|
8339
8653
|
inputs: fileInputs,
|
|
@@ -8345,7 +8659,7 @@ async function runQuery(opts) {
|
|
|
8345
8659
|
action: "queried"
|
|
8346
8660
|
});
|
|
8347
8661
|
const entries = [];
|
|
8348
|
-
const
|
|
8662
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
8349
8663
|
configDir: configDir ?? cwd,
|
|
8350
8664
|
base,
|
|
8351
8665
|
offline: opts.offline ?? config?.offline ?? false
|
|
@@ -8357,12 +8671,14 @@ async function runQuery(opts) {
|
|
|
8357
8671
|
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
8358
8672
|
);
|
|
8359
8673
|
}
|
|
8360
|
-
const
|
|
8674
|
+
const members = membersFor(label);
|
|
8675
|
+
const extracted = mergeExternalMetadata(
|
|
8361
8676
|
label,
|
|
8362
8677
|
extractor.extract(content, label, {
|
|
8363
|
-
elements: resolveElements(label, config)
|
|
8678
|
+
elements: resolveElements(label, config, members)
|
|
8364
8679
|
}),
|
|
8365
|
-
|
|
8680
|
+
externalMetadata,
|
|
8681
|
+
members,
|
|
8366
8682
|
base
|
|
8367
8683
|
).extracted;
|
|
8368
8684
|
entries.push({ label, extracted, extractor });
|
|
@@ -8396,8 +8712,12 @@ async function runQuery(opts) {
|
|
|
8396
8712
|
cwd,
|
|
8397
8713
|
configDir,
|
|
8398
8714
|
configPath,
|
|
8715
|
+
configSection,
|
|
8399
8716
|
trustRoot: schemaTrustRoot(cwd, configDir),
|
|
8400
|
-
|
|
8717
|
+
externalMetadata,
|
|
8718
|
+
collections,
|
|
8719
|
+
declaredCollections,
|
|
8720
|
+
memberships: membersFor,
|
|
8401
8721
|
onNotice: opts.onNotice
|
|
8402
8722
|
});
|
|
8403
8723
|
return { ...run, frame: { cwd, base: configDir ?? cwd, runBase: base } };
|
|
@@ -8440,7 +8760,7 @@ async function prepareDbTarget(resolved, display) {
|
|
|
8440
8760
|
var MissingCollectionView = class extends Error {
|
|
8441
8761
|
};
|
|
8442
8762
|
function refuseSqlError(err) {
|
|
8443
|
-
const message = err
|
|
8763
|
+
const message = errorMessage(err);
|
|
8444
8764
|
if (message.includes("UNIQUE constraint failed: docs._path")) {
|
|
8445
8765
|
throw new DocmetaError("That _path already exists in the corpus.");
|
|
8446
8766
|
}
|
|
@@ -8469,7 +8789,7 @@ async function runSql(sql, entries, ctx) {
|
|
|
8469
8789
|
db = new DatabaseSync(target ? target.resolved : ":memory:");
|
|
8470
8790
|
} catch (err) {
|
|
8471
8791
|
throw new DocmetaError(
|
|
8472
|
-
`Cannot open "${target?.display ?? ":memory:"}": ${err
|
|
8792
|
+
`Cannot open "${target?.display ?? ":memory:"}": ${errorMessage(err)}`
|
|
8473
8793
|
);
|
|
8474
8794
|
}
|
|
8475
8795
|
const dbInfo = target ? {
|
|
@@ -8485,10 +8805,9 @@ async function runSql(sql, entries, ctx) {
|
|
|
8485
8805
|
createCollectionViews(
|
|
8486
8806
|
db,
|
|
8487
8807
|
collectCollections(entries, {
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
...ctx.onNotice ? { onNotice: ctx.onNotice } : {}
|
|
8808
|
+
collections: ctx.declaredCollections,
|
|
8809
|
+
...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
|
|
8810
|
+
base: ctx.base
|
|
8492
8811
|
})
|
|
8493
8812
|
);
|
|
8494
8813
|
viewsBuilt = true;
|
|
@@ -8504,7 +8823,9 @@ async function runSql(sql, entries, ctx) {
|
|
|
8504
8823
|
);
|
|
8505
8824
|
}
|
|
8506
8825
|
const lower = sql.toLowerCase();
|
|
8507
|
-
const observesCatalog = lower.includes("sqlite_master") || lower.includes("sqlite_schema") || lower.includes("pragma") || /^(create|drop|alter)\b/i.test(head) || collectionNames(ctx.
|
|
8826
|
+
const observesCatalog = lower.includes("sqlite_master") || lower.includes("sqlite_schema") || lower.includes("pragma") || /^(create|drop|alter)\b/i.test(head) || collectionNames(ctx.declaredCollections).some(
|
|
8827
|
+
(n) => lower.includes(n.toLowerCase())
|
|
8828
|
+
);
|
|
8508
8829
|
if (observesCatalog) buildViews();
|
|
8509
8830
|
const sentinel = `manni:null:${randomBytes(16).toString("hex")}`;
|
|
8510
8831
|
db.function("explicit_null", () => sentinel);
|
|
@@ -8516,9 +8837,9 @@ async function runSql(sql, entries, ctx) {
|
|
|
8516
8837
|
stmt = db.prepare(sql);
|
|
8517
8838
|
columns = stmt.columns().map((c) => c.name);
|
|
8518
8839
|
} catch (err) {
|
|
8519
|
-
const message = err
|
|
8840
|
+
const message = errorMessage(err);
|
|
8520
8841
|
const missing = missingTableName(message);
|
|
8521
|
-
if (missing !== void 0 && !viewsBuilt && namesConfiguredCollection(missing, ctx.
|
|
8842
|
+
if (missing !== void 0 && !viewsBuilt && namesConfiguredCollection(missing, ctx.declaredCollections)) {
|
|
8522
8843
|
throw new MissingCollectionView();
|
|
8523
8844
|
}
|
|
8524
8845
|
refuseSqlError(err);
|
|
@@ -8592,9 +8913,9 @@ function missingTableName(message) {
|
|
|
8592
8913
|
const at = message.indexOf(prefix);
|
|
8593
8914
|
return at === -1 ? void 0 : message.slice(at + prefix.length);
|
|
8594
8915
|
}
|
|
8595
|
-
function namesConfiguredCollection(missing,
|
|
8916
|
+
function namesConfiguredCollection(missing, collections) {
|
|
8596
8917
|
const candidates = [missing, missing.replace(/^(main|temp)\./i, "")];
|
|
8597
|
-
return collectionNames(
|
|
8918
|
+
return collectionNames(collections).some(
|
|
8598
8919
|
(n) => candidates.some((c) => c.toLowerCase() === n.toLowerCase())
|
|
8599
8920
|
);
|
|
8600
8921
|
}
|
|
@@ -8913,7 +9234,7 @@ function displayPath(ctx, abs) {
|
|
|
8913
9234
|
}
|
|
8914
9235
|
function assertSchemaWriteWithin(ctx, abs) {
|
|
8915
9236
|
const within = posixRelative(ctx.trustRoot.dir, abs);
|
|
8916
|
-
if (within !== "" && !within.startsWith("..") && !
|
|
9237
|
+
if (within !== "" && !within.startsWith("..") && !isAbsolute5(within)) return;
|
|
8917
9238
|
throw new DocmetaError(
|
|
8918
9239
|
`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.`
|
|
8919
9240
|
);
|
|
@@ -9030,12 +9351,13 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
9030
9351
|
filePath: e.label,
|
|
9031
9352
|
fileSchema: e.extracted.data[FILE_SCHEMA_KEY],
|
|
9032
9353
|
config: ctx.config,
|
|
9354
|
+
memberOf: ctx.memberships(e.label),
|
|
9033
9355
|
fileBase: ctx.cwd,
|
|
9034
9356
|
trustRoot: ctx.trustRoot,
|
|
9035
9357
|
onNotice: ctx.onNotice
|
|
9036
9358
|
});
|
|
9037
9359
|
} catch (err) {
|
|
9038
|
-
throw new DocmetaError(`"${e.label}": ${err
|
|
9360
|
+
throw new DocmetaError(`"${e.label}": ${errorMessage(err)}`);
|
|
9039
9361
|
}
|
|
9040
9362
|
sources.add(resolved.source);
|
|
9041
9363
|
if (resolved.overrideIndex !== void 0) {
|
|
@@ -9056,7 +9378,11 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
9056
9378
|
const overrides = ctx.config?.overrides ?? [];
|
|
9057
9379
|
const named = [...groupIndexes].sort((a, b) => a - b).flatMap((i) => {
|
|
9058
9380
|
const o = overrides[i];
|
|
9059
|
-
|
|
9381
|
+
if (o?.collection === void 0) return [];
|
|
9382
|
+
const declared = ctx.collections.find((c) => c.name === o.collection);
|
|
9383
|
+
return [
|
|
9384
|
+
`${o.collection} (${overrideGlobs(declared?.paths).join(", ")})`
|
|
9385
|
+
];
|
|
9060
9386
|
});
|
|
9061
9387
|
const last = named[named.length - 1];
|
|
9062
9388
|
const remedy = named.length >= 2 && last !== void 0 ? `The run spans ${[named.slice(0, -1).join(", "), last].join(" and ")}; re-run over one group's files, or pass -s <schema> to name the contract directly.` : "Scope the run to one override group, or pass -s <schema> to name the contract directly.";
|
|
@@ -9321,7 +9647,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9321
9647
|
}
|
|
9322
9648
|
return void 0;
|
|
9323
9649
|
};
|
|
9324
|
-
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !
|
|
9650
|
+
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute5(raw) ? resolve12(configDir, raw) : raw;
|
|
9325
9651
|
const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
|
|
9326
9652
|
const seqResolvesToRunSet = (node) => {
|
|
9327
9653
|
if (!isSeq2(node)) return false;
|
|
@@ -9347,9 +9673,11 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9347
9673
|
}
|
|
9348
9674
|
return n;
|
|
9349
9675
|
};
|
|
9350
|
-
const
|
|
9676
|
+
const root = ctx.configSection === void 0 ? doc : doc.get(ctx.configSection, true);
|
|
9677
|
+
const keyOf2 = (key) => root === doc ? doc.get(key, true) : isMap4(root) ? root.get(key, true) : void 0;
|
|
9678
|
+
const topSeq = keyOf2("schemas");
|
|
9351
9679
|
let repointCount = repoint(topSeq);
|
|
9352
|
-
const overrides =
|
|
9680
|
+
const overrides = keyOf2("overrides");
|
|
9353
9681
|
if (isSeq2(overrides)) {
|
|
9354
9682
|
for (const entry of overrides.items) {
|
|
9355
9683
|
if (isMap4(entry)) repointCount += repoint(entry.get("schemas", true));
|
|
@@ -9364,7 +9692,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
9364
9692
|
if (!isMap4(item)) continue;
|
|
9365
9693
|
const raw = rawRefOf(item);
|
|
9366
9694
|
if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
|
|
9367
|
-
const abs =
|
|
9695
|
+
const abs = isAbsolute5(raw) ? raw : resolve12(configDir, raw);
|
|
9368
9696
|
if (foldPath(abs) !== wantPath) continue;
|
|
9369
9697
|
pinFrom = item.get("integrity");
|
|
9370
9698
|
item.set("integrity", edit.integrity.value);
|
|
@@ -9611,28 +9939,28 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9611
9939
|
}
|
|
9612
9940
|
changes.push({ file: from, renamed: to, written: false });
|
|
9613
9941
|
}
|
|
9614
|
-
|
|
9942
|
+
refuseExternalMetadataWrites(changes, entries, ctx);
|
|
9615
9943
|
return changes;
|
|
9616
9944
|
}
|
|
9617
9945
|
function validateNewPath(p, base) {
|
|
9618
9946
|
const refuse = (why) => {
|
|
9619
9947
|
throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
|
|
9620
9948
|
};
|
|
9621
|
-
if (
|
|
9949
|
+
if (isAbsolute5(p)) refuse("it is absolute");
|
|
9622
9950
|
if (p.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
9623
9951
|
refuse("it traverses upward");
|
|
9624
9952
|
}
|
|
9625
9953
|
const abs = resolve12(base, p);
|
|
9626
9954
|
if (abs === resolve12(base)) refuse("it points at the corpus root, not a file");
|
|
9627
|
-
if (!abs.startsWith(resolve12(base) +
|
|
9955
|
+
if (!abs.startsWith(resolve12(base) + sep5)) refuse("it escapes the corpus");
|
|
9628
9956
|
}
|
|
9629
|
-
function
|
|
9630
|
-
const index = ctx.
|
|
9957
|
+
function refuseExternalMetadataWrites(changes, entries, ctx) {
|
|
9958
|
+
const index = ctx.externalMetadata;
|
|
9631
9959
|
if (!index) return;
|
|
9632
|
-
const owned = (key) => index.owners.get(key);
|
|
9960
|
+
const owned = (key) => index.owners.get(key)?.[0]?.file;
|
|
9633
9961
|
const refuse = (file, key, owner) => {
|
|
9634
9962
|
throw new DocmetaError(
|
|
9635
|
-
`"${file}": "${key}" is owned by
|
|
9963
|
+
`"${file}": "${key}" is owned by manifest ${owner}; edit the manifest instead.`
|
|
9636
9964
|
);
|
|
9637
9965
|
};
|
|
9638
9966
|
const data = new Map(entries.map((e) => [e.label, e.extracted.data]));
|
|
@@ -9657,7 +9985,7 @@ function refuseSidecarWrites(changes, entries, ctx) {
|
|
|
9657
9985
|
if (entry !== void 0 && entry.size > 0) {
|
|
9658
9986
|
const manifest = [...entry.values()][0]?.file ?? "manifest";
|
|
9659
9987
|
throw new DocmetaError(
|
|
9660
|
-
`"${c.file}":
|
|
9988
|
+
`"${c.file}": manifest ${manifest} names it; rename the manifest entry first.`
|
|
9661
9989
|
);
|
|
9662
9990
|
}
|
|
9663
9991
|
continue;
|
|
@@ -9674,7 +10002,7 @@ function refuseSidecarWrites(changes, entries, ctx) {
|
|
|
9674
10002
|
if (entry !== void 0) {
|
|
9675
10003
|
const manifest = entry.values().next().value?.file ?? "manifest";
|
|
9676
10004
|
throw new DocmetaError(
|
|
9677
|
-
`"${c.file}": "${key}" is the field
|
|
10005
|
+
`"${c.file}": "${key}" is the field manifest ${manifest} joins on, and this document has an entry; change the manifest first.`
|
|
9678
10006
|
);
|
|
9679
10007
|
}
|
|
9680
10008
|
}
|
|
@@ -9738,7 +10066,9 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9738
10066
|
path,
|
|
9739
10067
|
content: extractor.apply("", ops.created, {
|
|
9740
10068
|
filePath: label,
|
|
9741
|
-
|
|
10069
|
+
// The new file's memberships, from its path alone. An INSERT has to
|
|
10070
|
+
// write through the element set a read of that file would use.
|
|
10071
|
+
elements: resolveElements(label, ctx.config, ctx.memberships(label))
|
|
9742
10072
|
}),
|
|
9743
10073
|
ensureDir: true
|
|
9744
10074
|
});
|
|
@@ -9747,12 +10077,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9747
10077
|
const entry = byLabel.get(label);
|
|
9748
10078
|
if (!entry) throw new DocmetaError(`No loaded entry for "${label}".`);
|
|
9749
10079
|
const content = await readFile9(path, "utf8");
|
|
9750
|
-
const
|
|
10080
|
+
const members = ctx.memberships(label);
|
|
10081
|
+
const current = mergeExternalMetadata(
|
|
9751
10082
|
label,
|
|
9752
10083
|
entry.extractor.extract(content, label, {
|
|
9753
|
-
elements: resolveElements(label, ctx.config)
|
|
10084
|
+
elements: resolveElements(label, ctx.config, members)
|
|
9754
10085
|
}),
|
|
9755
|
-
ctx.
|
|
10086
|
+
ctx.externalMetadata,
|
|
10087
|
+
members,
|
|
9756
10088
|
ctx.base
|
|
9757
10089
|
).extracted;
|
|
9758
10090
|
if (ops.cleared) {
|
|
@@ -9784,16 +10116,19 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9784
10116
|
}
|
|
9785
10117
|
const applied = entry.extractor.apply(content, ops.patch, {
|
|
9786
10118
|
filePath: label,
|
|
9787
|
-
|
|
10119
|
+
// The memberships the read above used. Without them a collection-scoped
|
|
10120
|
+
// element the read found has nowhere to be written.
|
|
10121
|
+
elements: resolveElements(label, ctx.config, members),
|
|
9788
10122
|
deletions: ops.deletions
|
|
9789
10123
|
});
|
|
9790
10124
|
if (ops.deletions.length > 0) {
|
|
9791
|
-
const check =
|
|
10125
|
+
const check = mergeExternalMetadata(
|
|
9792
10126
|
label,
|
|
9793
10127
|
entry.extractor.extract(applied, label, {
|
|
9794
|
-
elements: resolveElements(label, ctx.config)
|
|
10128
|
+
elements: resolveElements(label, ctx.config, members)
|
|
9795
10129
|
}),
|
|
9796
|
-
ctx.
|
|
10130
|
+
ctx.externalMetadata,
|
|
10131
|
+
members,
|
|
9797
10132
|
ctx.base
|
|
9798
10133
|
).extracted;
|
|
9799
10134
|
for (const key of ops.deletions) {
|
|
@@ -9846,7 +10181,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9846
10181
|
await rename2(r.from, r.to);
|
|
9847
10182
|
} catch (err) {
|
|
9848
10183
|
throw new DocmetaError(
|
|
9849
|
-
`Cannot move "${r.from}" to "${r.to}": ${err
|
|
10184
|
+
`Cannot move "${r.from}" to "${r.to}": ${errorMessage(err)}`
|
|
9850
10185
|
);
|
|
9851
10186
|
}
|
|
9852
10187
|
}
|
|
@@ -10091,11 +10426,12 @@ function addUsage(total, next) {
|
|
|
10091
10426
|
}
|
|
10092
10427
|
async function runFill(opts) {
|
|
10093
10428
|
const cwd = opts.cwd ?? process.cwd();
|
|
10094
|
-
const { config, inputs, base, configDir } = await resolveRunConfig({
|
|
10429
|
+
const { config, inputs, base, configDir, collections, fromCollections } = await resolveRunConfig({
|
|
10095
10430
|
cwd,
|
|
10096
10431
|
configPath: opts.configPath,
|
|
10097
10432
|
noConfig: opts.noConfig,
|
|
10098
10433
|
inputs: opts.inputs,
|
|
10434
|
+
...opts.collections !== void 0 ? { collections: opts.collections } : {},
|
|
10099
10435
|
onConfigLoaded: opts.onConfigLoaded
|
|
10100
10436
|
});
|
|
10101
10437
|
const schemaOptions = schemaLoadOptions({
|
|
@@ -10110,7 +10446,7 @@ async function runFill(opts) {
|
|
|
10110
10446
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
10111
10447
|
if (inputs.length === 0) {
|
|
10112
10448
|
throw new DocmetaError(
|
|
10113
|
-
"No files to fill. Pass paths/globs, or
|
|
10449
|
+
"No files to fill. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
10114
10450
|
);
|
|
10115
10451
|
}
|
|
10116
10452
|
const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
|
|
@@ -10159,8 +10495,9 @@ async function runFill(opts) {
|
|
|
10159
10495
|
const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
|
|
10160
10496
|
const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
|
|
10161
10497
|
const fillExts = opts.exts ?? forcedExtractor?.extensions;
|
|
10162
|
-
const fillExclude =
|
|
10163
|
-
const
|
|
10498
|
+
const fillExclude = opts.exclude ?? [];
|
|
10499
|
+
const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
|
|
10500
|
+
const { files: walked, gitignoreSkipped } = await resolveTargetSet({
|
|
10164
10501
|
inputs: fileInputs,
|
|
10165
10502
|
exts: fillExts,
|
|
10166
10503
|
exclude: fillExclude,
|
|
@@ -10172,6 +10509,7 @@ async function runFill(opts) {
|
|
|
10172
10509
|
onNotice: opts.onNotice
|
|
10173
10510
|
})
|
|
10174
10511
|
});
|
|
10512
|
+
const files = fromCollections ? retainMembers(walked, membersFor) : walked;
|
|
10175
10513
|
assertNonEmpty({
|
|
10176
10514
|
files,
|
|
10177
10515
|
inputs: fileInputs,
|
|
@@ -10206,7 +10544,7 @@ async function runFill(opts) {
|
|
|
10206
10544
|
provider = construct();
|
|
10207
10545
|
} catch (err) {
|
|
10208
10546
|
throw new DocmetaError(
|
|
10209
|
-
err instanceof InferenceError ? err.message : `Could not construct the "${providerName}" provider: ${err
|
|
10547
|
+
err instanceof InferenceError ? err.message : `Could not construct the "${providerName}" provider: ${errorMessage(err)}`
|
|
10210
10548
|
);
|
|
10211
10549
|
}
|
|
10212
10550
|
}
|
|
@@ -10221,7 +10559,7 @@ async function runFill(opts) {
|
|
|
10221
10559
|
let turnsUsed = 0;
|
|
10222
10560
|
let inFlight = 0;
|
|
10223
10561
|
const turnsExhausted = () => maxTurns != null && turnsUsed + inFlight >= maxTurns;
|
|
10224
|
-
const
|
|
10562
|
+
const externalMetadata = await loadExternalMetadata(collections, {
|
|
10225
10563
|
configDir: configDir ?? cwd,
|
|
10226
10564
|
base,
|
|
10227
10565
|
offline: opts.offline ?? config?.offline ?? false
|
|
@@ -10242,19 +10580,21 @@ async function runFill(opts) {
|
|
|
10242
10580
|
`The "${extractor.name}" format is read-only; manni meta fill cannot write metadata back to it.`
|
|
10243
10581
|
);
|
|
10244
10582
|
}
|
|
10245
|
-
const
|
|
10583
|
+
const members = membersFor(label);
|
|
10584
|
+
const elements = resolveElements(label, config, members);
|
|
10246
10585
|
let extracted;
|
|
10247
10586
|
let merged;
|
|
10248
10587
|
try {
|
|
10249
|
-
merged =
|
|
10588
|
+
merged = mergeExternalMetadata(
|
|
10250
10589
|
label,
|
|
10251
10590
|
extractor.extract(content, label, { elements }),
|
|
10252
|
-
|
|
10591
|
+
externalMetadata,
|
|
10592
|
+
members,
|
|
10253
10593
|
base
|
|
10254
10594
|
);
|
|
10255
10595
|
extracted = merged.extracted;
|
|
10256
10596
|
} catch (err) {
|
|
10257
|
-
return errorResult(label, extractor.name, err
|
|
10597
|
+
return errorResult(label, extractor.name, errorMessage(err));
|
|
10258
10598
|
}
|
|
10259
10599
|
let resolved;
|
|
10260
10600
|
try {
|
|
@@ -10263,6 +10603,7 @@ async function runFill(opts) {
|
|
|
10263
10603
|
fileSchema: extracted.data[FILE_SCHEMA_KEY],
|
|
10264
10604
|
cliSchemas: opts.cliSchemas,
|
|
10265
10605
|
config,
|
|
10606
|
+
memberOf: members,
|
|
10266
10607
|
// Same trust boundary as `validate`: `fill` writes metadata back into
|
|
10267
10608
|
// the document, so a schema a document chose for itself decides what
|
|
10268
10609
|
// gets written — if anything, a stronger reason to guard it.
|
|
@@ -10271,7 +10612,7 @@ async function runFill(opts) {
|
|
|
10271
10612
|
onNotice: opts.onNotice
|
|
10272
10613
|
});
|
|
10273
10614
|
} catch (err) {
|
|
10274
|
-
return errorResult(label, extractor.name, err
|
|
10615
|
+
return errorResult(label, extractor.name, errorMessage(err));
|
|
10275
10616
|
}
|
|
10276
10617
|
const schemaSet = resolved.schemas;
|
|
10277
10618
|
let schemas;
|
|
@@ -10299,16 +10640,16 @@ async function runFill(opts) {
|
|
|
10299
10640
|
existingErrors,
|
|
10300
10641
|
only
|
|
10301
10642
|
);
|
|
10302
|
-
const ownedCandidates =
|
|
10303
|
-
const
|
|
10304
|
-
return
|
|
10643
|
+
const ownedCandidates = externalMetadata ? candidates.flatMap((c) => {
|
|
10644
|
+
const owner = externalMetadata.owners.get(c.key)?.[0];
|
|
10645
|
+
return owner === void 0 ? [] : [{ key: c.key, file: owner.file }];
|
|
10305
10646
|
}) : [];
|
|
10306
10647
|
const firstOwned = ownedCandidates[0];
|
|
10307
10648
|
if (firstOwned) {
|
|
10308
10649
|
return errorResult(
|
|
10309
10650
|
label,
|
|
10310
10651
|
extractor.name,
|
|
10311
|
-
`"${firstOwned.key}" is owned by
|
|
10652
|
+
`"${firstOwned.key}" is owned by manifest ${firstOwned.file}; manni meta fill cannot write a manifest key. Add it to the manifest instead.`,
|
|
10312
10653
|
schemaSet
|
|
10313
10654
|
);
|
|
10314
10655
|
}
|
|
@@ -10325,7 +10666,7 @@ async function runFill(opts) {
|
|
|
10325
10666
|
try {
|
|
10326
10667
|
extractor.apply(content, {}, { filePath: label, elements });
|
|
10327
10668
|
} catch (err) {
|
|
10328
|
-
return errorResult(label, extractor.name, err
|
|
10669
|
+
return errorResult(label, extractor.name, errorMessage(err), schemaSet);
|
|
10329
10670
|
}
|
|
10330
10671
|
const cacheKey = buildCacheKey([
|
|
10331
10672
|
identity.provider,
|
|
@@ -10363,7 +10704,7 @@ async function runFill(opts) {
|
|
|
10363
10704
|
return errorResult(
|
|
10364
10705
|
label,
|
|
10365
10706
|
extractor.name,
|
|
10366
|
-
`Could not build a proposal schema from ${schemaSet.join(", ")}: ${err
|
|
10707
|
+
`Could not build a proposal schema from ${schemaSet.join(", ")}: ${errorMessage(err)}`,
|
|
10367
10708
|
schemaSet
|
|
10368
10709
|
);
|
|
10369
10710
|
}
|
|
@@ -10491,7 +10832,7 @@ async function runFill(opts) {
|
|
|
10491
10832
|
return errorResult(
|
|
10492
10833
|
label,
|
|
10493
10834
|
extractor.name,
|
|
10494
|
-
err
|
|
10835
|
+
errorMessage(err),
|
|
10495
10836
|
schemaSet,
|
|
10496
10837
|
fields
|
|
10497
10838
|
);
|
|
@@ -10791,7 +11132,7 @@ async function resolveIdentity(spec) {
|
|
|
10791
11132
|
return await resolveProviderIdentityAsync(spec);
|
|
10792
11133
|
} catch (err) {
|
|
10793
11134
|
throw new DocmetaError(
|
|
10794
|
-
err instanceof InferenceError ? err.message : `Could not resolve provider "${spec.provider}": ${err
|
|
11135
|
+
err instanceof InferenceError ? err.message : `Could not resolve provider "${spec.provider}": ${errorMessage(err)}`
|
|
10795
11136
|
);
|
|
10796
11137
|
}
|
|
10797
11138
|
}
|
|
@@ -10838,19 +11179,19 @@ async function mapConcurrent(items, limit, fn) {
|
|
|
10838
11179
|
}
|
|
10839
11180
|
|
|
10840
11181
|
// src/meta/reporters/sarif.ts
|
|
10841
|
-
import { isAbsolute as
|
|
11182
|
+
import { isAbsolute as isAbsolute6 } from "node:path";
|
|
10842
11183
|
|
|
10843
11184
|
// src/meta/reporters/rule-id.ts
|
|
10844
11185
|
var SYNTHETIC_SCHEMA = "(parse)";
|
|
10845
11186
|
var PARSE_ERROR_RULE = "manni/parse-error";
|
|
10846
11187
|
var SCHEMA_ERROR_RULE = "manni/schema-error";
|
|
10847
|
-
var
|
|
10848
|
-
var
|
|
11188
|
+
var EXTERNAL_OWNED_RULE = "external:owned/external";
|
|
11189
|
+
var EXTERNAL_DUPLICATE_RULE = "external:duplicate/external";
|
|
10849
11190
|
var RESERVED_RULES = {
|
|
10850
11191
|
[PARSE_ERROR_RULE]: "The document's metadata block could not be parsed.",
|
|
10851
11192
|
[SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document.",
|
|
10852
|
-
[
|
|
10853
|
-
[
|
|
11193
|
+
[EXTERNAL_OWNED_RULE]: "The document carries a key a manifest owns; the manifest is the only place that key may be set.",
|
|
11194
|
+
[EXTERNAL_DUPLICATE_RULE]: "Two documents carry the same value of a manifest's join field, so one manifest entry matched both."
|
|
10854
11195
|
};
|
|
10855
11196
|
function ruleIdFor(e, frame) {
|
|
10856
11197
|
if (e.schema === SYNTHETIC_SCHEMA) {
|
|
@@ -10878,7 +11219,7 @@ function uriFrame(frame) {
|
|
|
10878
11219
|
}
|
|
10879
11220
|
function artifactUri(file, frame) {
|
|
10880
11221
|
const uri = canonicalFilePath(file, frame);
|
|
10881
|
-
if (uri === "" || uri.startsWith("../") ||
|
|
11222
|
+
if (uri === "" || uri.startsWith("../") || isAbsolute6(uri)) return null;
|
|
10882
11223
|
return uri;
|
|
10883
11224
|
}
|
|
10884
11225
|
function renderSarif(results, opts = {}) {
|
|
@@ -11416,8 +11757,13 @@ export {
|
|
|
11416
11757
|
palette,
|
|
11417
11758
|
shouldColor,
|
|
11418
11759
|
ToolError,
|
|
11760
|
+
errorMessage,
|
|
11419
11761
|
programName,
|
|
11420
11762
|
setProgramName,
|
|
11763
|
+
externalMetadataUrlProblem,
|
|
11764
|
+
parseCollections,
|
|
11765
|
+
selectCollections,
|
|
11766
|
+
isMember,
|
|
11421
11767
|
findConfigFile,
|
|
11422
11768
|
readConfigFile,
|
|
11423
11769
|
escapeWorkflowCommandMessage,
|
|
@@ -11443,32 +11789,32 @@ export {
|
|
|
11443
11789
|
collectSchemaPins,
|
|
11444
11790
|
rebaseConfigSchemaRefs,
|
|
11445
11791
|
resolveSchemaSet,
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
fetchSidecar,
|
|
11792
|
+
EXTERNAL_METADATA_FETCH_TIMEOUT_MS,
|
|
11793
|
+
EXTERNAL_METADATA_FETCH_MAX_BYTES,
|
|
11794
|
+
fetchExternalMetadata,
|
|
11450
11795
|
locateFrontmatter,
|
|
11451
11796
|
frontmatterInnerText,
|
|
11452
11797
|
extractFrontmatter,
|
|
11453
11798
|
applyFrontmatter,
|
|
11454
11799
|
extractorForExtension,
|
|
11455
11800
|
supportedExtensions,
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11801
|
+
EXTERNAL_OWNED_SCHEMA,
|
|
11802
|
+
EXTERNAL_DUPLICATE_SCHEMA,
|
|
11803
|
+
EXTERNAL_KEYWORD,
|
|
11459
11804
|
PATH_JOIN,
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11805
|
+
externalMetadataJoin,
|
|
11806
|
+
loadExternalMetadata,
|
|
11807
|
+
mergeExternalMetadata,
|
|
11808
|
+
externalMetadataPointer,
|
|
11464
11809
|
orphanEntries,
|
|
11465
11810
|
orphanJoins,
|
|
11466
11811
|
orphanError,
|
|
11467
11812
|
parseConfig,
|
|
11468
11813
|
loadConfig,
|
|
11469
11814
|
resolveRunConfig,
|
|
11470
|
-
Validator,
|
|
11471
11815
|
isParamName,
|
|
11816
|
+
memberOf,
|
|
11817
|
+
Validator,
|
|
11472
11818
|
rowsToFindings,
|
|
11473
11819
|
runValidate,
|
|
11474
11820
|
runGet,
|
|
@@ -11504,4 +11850,4 @@ export {
|
|
|
11504
11850
|
renderQuery,
|
|
11505
11851
|
renderQueryCsv
|
|
11506
11852
|
};
|
|
11507
|
-
//# sourceMappingURL=chunk-
|
|
11853
|
+
//# sourceMappingURL=chunk-IRHWILCO.js.map
|