@hawkeyexl/manni 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Y6FSY35O.js → chunk-7M2YOXVG.js} +1273 -580
- package/dist/chunk-7M2YOXVG.js.map +1 -0
- package/dist/{chunk-HJZFU2P5.js → chunk-BKJOQHWN.js} +3 -2
- package/dist/cli.js +882 -6
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +228 -2
- package/dist/index.js +31 -1
- package/package.json +3 -1
- package/dist/chunk-Y6FSY35O.js.map +0 -1
- /package/dist/{chunk-HJZFU2P5.js.map → chunk-BKJOQHWN.js.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hawkeyexl/manni",
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.3.0",
|
|
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: {
|
|
@@ -78,6 +78,7 @@ var package_default = {
|
|
|
78
78
|
esbuild: "^0.28.2"
|
|
79
79
|
},
|
|
80
80
|
dependencies: {
|
|
81
|
+
"@axe-core/playwright": "^4.13.0",
|
|
81
82
|
"@hawkeyexl/inference": "^0.3.1",
|
|
82
83
|
"@xmldom/xmldom": "^0.9.12",
|
|
83
84
|
ajv: "^8.17.1",
|
|
@@ -88,6 +89,7 @@ var package_default = {
|
|
|
88
89
|
parse5: "^8.0.1",
|
|
89
90
|
picocolors: "^1.1.1",
|
|
90
91
|
picomatch: "^4.0.7",
|
|
92
|
+
"playwright-core": "^1.63.0",
|
|
91
93
|
"smol-toml": "^1.8.0",
|
|
92
94
|
yaml: "^2.6.1"
|
|
93
95
|
},
|
|
@@ -152,8 +154,8 @@ var RENAME_ATTEMPTS = 3;
|
|
|
152
154
|
function errorCode(err) {
|
|
153
155
|
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
154
156
|
}
|
|
155
|
-
var wait = (ms) => new Promise((
|
|
156
|
-
setTimeout(
|
|
157
|
+
var wait = (ms) => new Promise((resolve14) => {
|
|
158
|
+
setTimeout(resolve14, ms);
|
|
157
159
|
});
|
|
158
160
|
async function writeFileAtomic(path, contents) {
|
|
159
161
|
const tmp = join(
|
|
@@ -2609,11 +2611,17 @@ var BUILTINS = /* @__PURE__ */ new Map([
|
|
|
2609
2611
|
["anthropic:claude-subagent:2.1", __default23]
|
|
2610
2612
|
]);
|
|
2611
2613
|
function assertPublishableBuiltinId(id) {
|
|
2612
|
-
|
|
2614
|
+
const first = id.split(":")[0];
|
|
2615
|
+
if (first === "check") {
|
|
2613
2616
|
throw new Error(
|
|
2614
2617
|
`Built-in id "${id}" is not publishable: the "check" first segment is reserved for corpus check identities (check:<name>, proposal 0026).`
|
|
2615
2618
|
);
|
|
2616
2619
|
}
|
|
2620
|
+
if (first === "sidecar") {
|
|
2621
|
+
throw new Error(
|
|
2622
|
+
`Built-in id "${id}" is not publishable: the "sidecar" first segment is reserved for sidecar finding identities (sidecar:owned, proposal 0037).`
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2617
2625
|
}
|
|
2618
2626
|
for (const id of BUILTINS.keys()) assertPublishableBuiltinId(id);
|
|
2619
2627
|
function listBuiltins() {
|
|
@@ -2741,8 +2749,8 @@ var SCHEMA_KEYS = [
|
|
|
2741
2749
|
"multipleOf"
|
|
2742
2750
|
];
|
|
2743
2751
|
var RETRY_DELAY_MS = 500;
|
|
2744
|
-
var wait2 = (ms) => new Promise((
|
|
2745
|
-
setTimeout(
|
|
2752
|
+
var wait2 = (ms) => new Promise((resolve14) => {
|
|
2753
|
+
setTimeout(resolve14, ms);
|
|
2746
2754
|
});
|
|
2747
2755
|
var RetryableFetchError = class extends DocmetaError {
|
|
2748
2756
|
};
|
|
@@ -2994,6 +3002,277 @@ async function loadSchema(ref, options = {}) {
|
|
|
2994
3002
|
}
|
|
2995
3003
|
}
|
|
2996
3004
|
|
|
3005
|
+
// src/meta/core/resolve-schema.ts
|
|
3006
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3007
|
+
import picomatch from "picomatch";
|
|
3008
|
+
var DEFAULT_SCHEMAS = Object.freeze([
|
|
3009
|
+
"google:okf:0.1",
|
|
3010
|
+
"passo-uno:seven-action:1.0"
|
|
3011
|
+
]);
|
|
3012
|
+
var FILE_SCHEMA_KEY = "$schema";
|
|
3013
|
+
function schemaEntryRef(entry) {
|
|
3014
|
+
return typeof entry === "string" ? entry : entry.ref;
|
|
3015
|
+
}
|
|
3016
|
+
function collectSchemaPins(config) {
|
|
3017
|
+
const pins = /* @__PURE__ */ new Map();
|
|
3018
|
+
for (const entry of config?.schemas ?? []) {
|
|
3019
|
+
if (typeof entry === "string") continue;
|
|
3020
|
+
if (entry.source === void 0 && entry.integrity === void 0) continue;
|
|
3021
|
+
pins.set(entry.ref, {
|
|
3022
|
+
...entry.source !== void 0 ? { source: entry.source } : {},
|
|
3023
|
+
...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
return pins;
|
|
3027
|
+
}
|
|
3028
|
+
function rebaseConfigSchemaRefs(config, configDir, cwd) {
|
|
3029
|
+
if (resolve(configDir) === resolve(cwd)) return config;
|
|
3030
|
+
const rebase = (ref) => classifyRef(ref).kind === "file" && !isAbsolute(ref) ? resolve(configDir, ref) : ref;
|
|
3031
|
+
const rebaseEntry = (entry) => typeof entry === "string" ? rebase(entry) : {
|
|
3032
|
+
...entry,
|
|
3033
|
+
ref: rebase(entry.ref),
|
|
3034
|
+
...entry.source !== void 0 ? { source: rebase(entry.source) } : {}
|
|
3035
|
+
};
|
|
3036
|
+
return {
|
|
3037
|
+
...config,
|
|
3038
|
+
...config.schemas ? { schemas: config.schemas.map(rebaseEntry) } : {},
|
|
3039
|
+
...config.overrides ? {
|
|
3040
|
+
overrides: config.overrides.map((o) => ({
|
|
3041
|
+
...o,
|
|
3042
|
+
schemas: o.schemas.map(rebase)
|
|
3043
|
+
}))
|
|
3044
|
+
} : {}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
var matcherCache = /* @__PURE__ */ new Map();
|
|
3048
|
+
function overrideGlobs(files) {
|
|
3049
|
+
return typeof files === "string" ? [files] : files;
|
|
3050
|
+
}
|
|
3051
|
+
function matchesFileGlob(glob, filePath) {
|
|
3052
|
+
return matches(glob, filePath);
|
|
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);
|
|
3060
|
+
}
|
|
3061
|
+
return m(filePath.replace(/\\/g, "/"));
|
|
3062
|
+
}
|
|
3063
|
+
function coerceFileSchema(value) {
|
|
3064
|
+
if (value === void 0 || value === null) return void 0;
|
|
3065
|
+
if (typeof value === "string") return [value];
|
|
3066
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
3067
|
+
return value;
|
|
3068
|
+
}
|
|
3069
|
+
throw new Error(
|
|
3070
|
+
`Invalid "${FILE_SCHEMA_KEY}": must be a string or a list of strings.`
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
function dedupe(refs) {
|
|
3074
|
+
return [...new Set(refs)];
|
|
3075
|
+
}
|
|
3076
|
+
function hostAllowed(url, hosts) {
|
|
3077
|
+
const hostname = url.hostname.toLowerCase();
|
|
3078
|
+
const host = url.host.toLowerCase();
|
|
3079
|
+
return hosts.some((h) => {
|
|
3080
|
+
const want = h.trim().toLowerCase();
|
|
3081
|
+
return want === hostname || want === host;
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
function assertDocumentRefAllowed(ref, mode, params) {
|
|
3085
|
+
const { kind } = classifyRef(ref);
|
|
3086
|
+
if (kind === "builtin" || isPublishedBuiltinUrl(ref)) return;
|
|
3087
|
+
if (kind === "url") {
|
|
3088
|
+
if (mode === "local") {
|
|
3089
|
+
throw new DocmetaError(
|
|
3090
|
+
`Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": schemaTrust.documentRefs is "local", so a document may name a built-in id or a schema file inside the repository, but not a URL. Vendor it with \`manni meta schemas vendor ${ref}\` and reference the local copy, or put the URL in \`schemas:\` where an operator controls it.`
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
const hosts = params.config?.schemaTrust?.hosts;
|
|
3094
|
+
if (!hosts || hosts.length === 0) return;
|
|
3095
|
+
let url;
|
|
3096
|
+
try {
|
|
3097
|
+
url = new URL(ref);
|
|
3098
|
+
} catch {
|
|
3099
|
+
throw new DocmetaError(
|
|
3100
|
+
`Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": it cannot be parsed as a URL, so it cannot be checked against schemaTrust.hosts.`
|
|
3101
|
+
);
|
|
3102
|
+
}
|
|
3103
|
+
if (!hostAllowed(url, hosts)) {
|
|
3104
|
+
throw new DocmetaError(
|
|
3105
|
+
`Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": host "${url.host}" is not in schemaTrust.hosts (${hosts.join(", ")}). Add the host there, or reference a schema the config already names.`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
const root = params.trustRoot;
|
|
3111
|
+
if (!root) return;
|
|
3112
|
+
const abs = resolve(params.fileBase ?? process.cwd(), ref);
|
|
3113
|
+
const within = relative(root.dir, abs);
|
|
3114
|
+
if (within !== "" && !within.startsWith("..") && !isAbsolute(within)) return;
|
|
3115
|
+
const boundary = root.source === "git" ? `the repository root (${root.dir})` : root.source === "config" ? `${root.dir} \u2014 no git repository was found, so the config file's own directory is the boundary` : `${root.dir} \u2014 no git repository and no config file were found, so the directory the command was run from is the boundary`;
|
|
3116
|
+
const scope = root.source === "git" ? "the repository" : "the project";
|
|
3117
|
+
throw new DocmetaError(
|
|
3118
|
+
`Refusing the "${FILE_SCHEMA_KEY}" path "${ref}": it resolves outside ${boundary}. A document may only name a schema inside ${scope}; a path in \`schemas:\` or on \`--schema\` is not restricted this way.`
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
3121
|
+
function resolveSchemaSetWithSource(params) {
|
|
3122
|
+
const { filePath, fileSchema, cliSchemas, config } = params;
|
|
3123
|
+
if (cliSchemas && cliSchemas.length > 0) {
|
|
3124
|
+
return { schemas: dedupe(cliSchemas), source: "cli" };
|
|
3125
|
+
}
|
|
3126
|
+
const fromFile = coerceFileSchema(fileSchema);
|
|
3127
|
+
if (fromFile && fromFile.length > 0) {
|
|
3128
|
+
const mode = config?.schemaTrust?.documentRefs ?? "any";
|
|
3129
|
+
if (mode === "none") {
|
|
3130
|
+
params.onNotice?.(
|
|
3131
|
+
`${filePath}: "${FILE_SCHEMA_KEY}" is ignored (${fromFile.join(", ")}) \u2014 schemaTrust.documentRefs is "none", so the schema set comes from the config instead. Remove the key from the document, or set schemaTrust.documentRefs to "local" or "any" to honor it.`
|
|
3132
|
+
);
|
|
3133
|
+
} else {
|
|
3134
|
+
for (const ref of fromFile) assertDocumentRefAllowed(ref, mode, params);
|
|
3135
|
+
return { schemas: dedupe(fromFile), source: "document" };
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
if (config?.overrides) {
|
|
3139
|
+
for (const [i, ov] of config.overrides.entries()) {
|
|
3140
|
+
if (matches(ov.files, filePath) && ov.schemas.length > 0) {
|
|
3141
|
+
return { schemas: dedupe(ov.schemas), source: "override", overrideIndex: i };
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (config?.schemas && config.schemas.length > 0) {
|
|
3146
|
+
return {
|
|
3147
|
+
schemas: dedupe(config.schemas.map(schemaEntryRef)),
|
|
3148
|
+
source: "config"
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
return { schemas: [...DEFAULT_SCHEMAS], source: "default" };
|
|
3152
|
+
}
|
|
3153
|
+
function resolveSchemaSet(params) {
|
|
3154
|
+
return resolveSchemaSetWithSource(params).schemas;
|
|
3155
|
+
}
|
|
3156
|
+
function resolveElements(filePath, config) {
|
|
3157
|
+
const out = [];
|
|
3158
|
+
if (config?.elements) out.push(...config.elements);
|
|
3159
|
+
for (const ov of config?.overrides ?? []) {
|
|
3160
|
+
if (ov.elements && matches(ov.files, filePath)) out.push(...ov.elements);
|
|
3161
|
+
}
|
|
3162
|
+
return dedupe(out);
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// src/meta/core/sidecar-fetch.ts
|
|
3166
|
+
var SIDECAR_FETCH_TIMEOUT_MS = 1e4;
|
|
3167
|
+
var SIDECAR_FETCH_MAX_BYTES = 5 * 1024 * 1024;
|
|
3168
|
+
var MAX_REDIRECTS = 5;
|
|
3169
|
+
var RETRY_DELAY_MS2 = 500;
|
|
3170
|
+
function sidecarUrlProblem(url) {
|
|
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 = {}) {
|
|
3191
|
+
if (opts.offline) {
|
|
3192
|
+
throw new DocmetaError(
|
|
3193
|
+
`Sidecar manifest ${url} is remote and the run is offline. Vendor it to a path, or drop --offline.`
|
|
3194
|
+
);
|
|
3195
|
+
}
|
|
3196
|
+
const timeoutMs = opts.timeoutMs ?? SIDECAR_FETCH_TIMEOUT_MS;
|
|
3197
|
+
const maxBytes = opts.maxBytes ?? SIDECAR_FETCH_MAX_BYTES;
|
|
3198
|
+
const env = opts.env ?? process.env;
|
|
3199
|
+
let token;
|
|
3200
|
+
if (opts.tokenEnv !== void 0) {
|
|
3201
|
+
token = env[opts.tokenEnv];
|
|
3202
|
+
if (token === void 0 || token === "") {
|
|
3203
|
+
throw new DocmetaError(
|
|
3204
|
+
`Sidecar manifest ${url}: the environment variable ${opts.tokenEnv} named by "tokenEnv" is not set.`
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
let res = await requestWithRedirects(url, token, timeoutMs);
|
|
3209
|
+
if (res instanceof Error || res.status >= 500) {
|
|
3210
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS2));
|
|
3211
|
+
res = await requestWithRedirects(url, token, timeoutMs);
|
|
3212
|
+
}
|
|
3213
|
+
if (res instanceof Error) {
|
|
3214
|
+
throw new DocmetaError(
|
|
3215
|
+
`Sidecar manifest ${url} could not be fetched: ${res.message}`
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
if (!res.ok) {
|
|
3219
|
+
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
|
+
throw new DocmetaError(
|
|
3221
|
+
`Sidecar manifest ${url} could not be fetched: HTTP ${res.status}${hint}.`
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
return readCapped(url, res, maxBytes);
|
|
3225
|
+
}
|
|
3226
|
+
async function requestWithRedirects(url, token, timeoutMs) {
|
|
3227
|
+
const origin = new URL(url).origin;
|
|
3228
|
+
let current = url;
|
|
3229
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
3230
|
+
const sameOrigin = new URL(current).origin === origin;
|
|
3231
|
+
const headers = { accept: "text/plain, application/x-yaml, */*" };
|
|
3232
|
+
if (token !== void 0 && sameOrigin) headers.authorization = `Bearer ${token}`;
|
|
3233
|
+
let res;
|
|
3234
|
+
try {
|
|
3235
|
+
res = await fetch(current, {
|
|
3236
|
+
headers,
|
|
3237
|
+
redirect: "manual",
|
|
3238
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
3239
|
+
});
|
|
3240
|
+
} catch (err) {
|
|
3241
|
+
const isAbort2 = err instanceof Error && err.name === "TimeoutError";
|
|
3242
|
+
return isAbort2 ? new Error(`timed out after ${timeoutMs}ms`) : new Error(err instanceof Error ? err.message : String(err));
|
|
3243
|
+
}
|
|
3244
|
+
if (res.status >= 300 && res.status < 400) {
|
|
3245
|
+
const location = res.headers.get("location");
|
|
3246
|
+
if (location === null) return res;
|
|
3247
|
+
await res.body?.cancel();
|
|
3248
|
+
current = new URL(location, current).toString();
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
return res;
|
|
3252
|
+
}
|
|
3253
|
+
return new Error(`more than ${MAX_REDIRECTS} redirects`);
|
|
3254
|
+
}
|
|
3255
|
+
async function readCapped(url, res, maxBytes) {
|
|
3256
|
+
const body = res.body;
|
|
3257
|
+
if (!body) return "";
|
|
3258
|
+
const reader = body.getReader();
|
|
3259
|
+
const chunks = [];
|
|
3260
|
+
let total = 0;
|
|
3261
|
+
for (; ; ) {
|
|
3262
|
+
const { done, value } = await reader.read();
|
|
3263
|
+
if (done || value === void 0) break;
|
|
3264
|
+
total += value.byteLength;
|
|
3265
|
+
if (total > maxBytes) {
|
|
3266
|
+
await reader.cancel();
|
|
3267
|
+
throw new DocmetaError(
|
|
3268
|
+
`Sidecar manifest ${url} is too large: the response exceeds the ${maxBytes}-byte limit.`
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
chunks.push(value);
|
|
3272
|
+
}
|
|
3273
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3274
|
+
}
|
|
3275
|
+
|
|
2997
3276
|
// src/meta/extractors/frontmatter.ts
|
|
2998
3277
|
import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
|
|
2999
3278
|
import { parse as parseToml } from "smol-toml";
|
|
@@ -3753,8 +4032,8 @@ function ditaShape(content, root, filePath) {
|
|
|
3753
4032
|
const lower = filePath?.toLowerCase() ?? "";
|
|
3754
4033
|
const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
|
|
3755
4034
|
if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
|
|
3756
|
-
const
|
|
3757
|
-
return
|
|
4035
|
+
const isMap5 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
|
|
4036
|
+
return isMap5 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
|
|
3758
4037
|
kind: "topic",
|
|
3759
4038
|
container: "metadata",
|
|
3760
4039
|
preamble: /* @__PURE__ */ new Set([
|
|
@@ -5179,202 +5458,477 @@ function listFormats() {
|
|
|
5179
5458
|
}));
|
|
5180
5459
|
}
|
|
5181
5460
|
|
|
5182
|
-
// src/meta/core/
|
|
5183
|
-
import {
|
|
5184
|
-
import
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5461
|
+
// src/meta/core/sidecars.ts
|
|
5462
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
5463
|
+
import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
5464
|
+
import {
|
|
5465
|
+
LineCounter as LineCounter2,
|
|
5466
|
+
isMap as isMap3,
|
|
5467
|
+
isNode,
|
|
5468
|
+
isScalar as isScalar2,
|
|
5469
|
+
parseDocument as parseDocument3
|
|
5470
|
+
} from "yaml";
|
|
5471
|
+
|
|
5472
|
+
// src/meta/core/load-files.ts
|
|
5473
|
+
import { stat as stat3 } from "node:fs/promises";
|
|
5474
|
+
import { extname, relative as relative3, resolve as resolve4 } from "node:path";
|
|
5475
|
+
import fg from "fast-glob";
|
|
5476
|
+
import picomatch2 from "picomatch";
|
|
5477
|
+
|
|
5478
|
+
// src/meta/core/gitignore.ts
|
|
5479
|
+
import { spawn } from "node:child_process";
|
|
5480
|
+
import { dirname as dirname3, relative as relative2, resolve as resolve3, sep } from "node:path";
|
|
5481
|
+
|
|
5482
|
+
// src/shared/git-root.ts
|
|
5483
|
+
import { existsSync } from "node:fs";
|
|
5484
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
5485
|
+
function findGitRoot(cwd) {
|
|
5486
|
+
let dir = resolve2(cwd);
|
|
5487
|
+
for (; ; ) {
|
|
5488
|
+
if (existsSync(join3(dir, ".git"))) return dir;
|
|
5489
|
+
const parent = dirname2(dir);
|
|
5490
|
+
if (parent === dir) return null;
|
|
5491
|
+
dir = parent;
|
|
5202
5492
|
}
|
|
5203
|
-
return pins;
|
|
5204
5493
|
}
|
|
5205
|
-
function
|
|
5206
|
-
|
|
5207
|
-
const
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5494
|
+
function searchPath(cwd) {
|
|
5495
|
+
const start = resolve2(cwd);
|
|
5496
|
+
const root = findGitRoot(start);
|
|
5497
|
+
if (root === null) return [start];
|
|
5498
|
+
const chain = [];
|
|
5499
|
+
let dir = start;
|
|
5500
|
+
for (; ; ) {
|
|
5501
|
+
chain.push(dir);
|
|
5502
|
+
if (dir === root) return chain;
|
|
5503
|
+
const parent = dirname2(dir);
|
|
5504
|
+
if (parent === dir) return chain;
|
|
5505
|
+
dir = parent;
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5509
|
+
// src/meta/core/gitignore.ts
|
|
5510
|
+
var GITIGNORE_UNAVAILABLE = ".gitignore filtering was requested but git could not answer here (no repository, or no git on PATH); no files were skipped.";
|
|
5511
|
+
var unavailable = () => ({
|
|
5512
|
+
ignored: /* @__PURE__ */ new Set(),
|
|
5513
|
+
available: false
|
|
5514
|
+
});
|
|
5515
|
+
async function gitIgnored(paths, cwd) {
|
|
5516
|
+
if (paths.length === 0) {
|
|
5517
|
+
return { ignored: /* @__PURE__ */ new Set(), available: true };
|
|
5518
|
+
}
|
|
5519
|
+
const rootOf = /* @__PURE__ */ new Map();
|
|
5520
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
5521
|
+
let inSomeRepo = false;
|
|
5522
|
+
for (const spelled of paths) {
|
|
5523
|
+
const abs = resolve3(cwd, spelled);
|
|
5524
|
+
const dir = dirname3(abs);
|
|
5525
|
+
let root = rootOf.get(dir);
|
|
5526
|
+
if (root === void 0) {
|
|
5527
|
+
root = findGitRoot(dir);
|
|
5528
|
+
rootOf.set(dir, root);
|
|
5529
|
+
}
|
|
5530
|
+
if (root === null) continue;
|
|
5531
|
+
inSomeRepo = true;
|
|
5532
|
+
const rel = relative2(root, abs).split(sep).join("/");
|
|
5533
|
+
const bucket = byRoot.get(root);
|
|
5534
|
+
if (bucket) bucket.push({ rel, spelled });
|
|
5535
|
+
else byRoot.set(root, [{ rel, spelled }]);
|
|
5536
|
+
}
|
|
5537
|
+
if (!inSomeRepo) return unavailable();
|
|
5538
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
5539
|
+
let answered = 0;
|
|
5540
|
+
for (const [root, bucket] of byRoot) {
|
|
5541
|
+
const answer = await checkIgnore(
|
|
5542
|
+
bucket.map((b) => b.rel),
|
|
5543
|
+
root
|
|
5544
|
+
);
|
|
5545
|
+
if (!answer.available) continue;
|
|
5546
|
+
answered += 1;
|
|
5547
|
+
for (const b of bucket) {
|
|
5548
|
+
if (answer.ignored.has(b.rel)) ignored.add(b.spelled);
|
|
5549
|
+
}
|
|
5550
|
+
}
|
|
5551
|
+
return answered > 0 ? { ignored, available: true } : unavailable();
|
|
5552
|
+
}
|
|
5553
|
+
function checkIgnore(paths, cwd) {
|
|
5554
|
+
return new Promise((settle) => {
|
|
5555
|
+
let done = false;
|
|
5556
|
+
const finish = (answer) => {
|
|
5557
|
+
if (done) return;
|
|
5558
|
+
done = true;
|
|
5559
|
+
settle(answer);
|
|
5560
|
+
};
|
|
5561
|
+
let child;
|
|
5562
|
+
try {
|
|
5563
|
+
child = spawn("git", ["check-ignore", "--stdin", "-z"], {
|
|
5564
|
+
cwd,
|
|
5565
|
+
windowsHide: true
|
|
5566
|
+
});
|
|
5567
|
+
} catch {
|
|
5568
|
+
finish(unavailable());
|
|
5569
|
+
return;
|
|
5570
|
+
}
|
|
5571
|
+
let out = "";
|
|
5572
|
+
child.stdout.setEncoding("utf8");
|
|
5573
|
+
child.stdout.on("data", (chunk) => {
|
|
5574
|
+
out += chunk;
|
|
5575
|
+
});
|
|
5576
|
+
child.stderr.resume();
|
|
5577
|
+
child.on("error", () => {
|
|
5578
|
+
finish(unavailable());
|
|
5579
|
+
});
|
|
5580
|
+
child.stdin.on("error", () => {
|
|
5581
|
+
});
|
|
5582
|
+
child.on("close", (code) => {
|
|
5583
|
+
if (code !== 0 && code !== 1) {
|
|
5584
|
+
finish(unavailable());
|
|
5585
|
+
return;
|
|
5586
|
+
}
|
|
5587
|
+
finish({
|
|
5588
|
+
ignored: new Set(out.split("\0").filter((p) => p !== "")),
|
|
5589
|
+
available: true
|
|
5590
|
+
});
|
|
5591
|
+
});
|
|
5592
|
+
child.stdin.end(paths.join("\0"));
|
|
5593
|
+
});
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5596
|
+
// src/meta/core/load-files.ts
|
|
5597
|
+
var DEFAULT_IGNORE = ["**/node_modules/**", "**/.git/**"];
|
|
5598
|
+
var STDIN_TOKEN = "-";
|
|
5599
|
+
var STDIN_LABEL = "<stdin>";
|
|
5600
|
+
function toPosix(p) {
|
|
5601
|
+
return p.replace(/\\/g, "/");
|
|
5602
|
+
}
|
|
5603
|
+
async function statOrNull(p) {
|
|
5604
|
+
try {
|
|
5605
|
+
return await stat3(p);
|
|
5606
|
+
} catch {
|
|
5607
|
+
return null;
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
function gitignoreOptions(opts) {
|
|
5611
|
+
const asked = opts.flag === true || opts.flag === void 0 && opts.configured === true;
|
|
5612
|
+
const notice = opts.onNotice;
|
|
5213
5613
|
return {
|
|
5214
|
-
|
|
5215
|
-
...
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
schemas: o.schemas.map(rebase)
|
|
5220
|
-
}))
|
|
5614
|
+
respectGitignore: opts.flag ?? opts.configured ?? true,
|
|
5615
|
+
...asked && notice ? {
|
|
5616
|
+
onGitignoreUnavailable: () => {
|
|
5617
|
+
notice(GITIGNORE_UNAVAILABLE);
|
|
5618
|
+
}
|
|
5221
5619
|
} : {}
|
|
5222
5620
|
};
|
|
5223
5621
|
}
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
}
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
const
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5622
|
+
async function resolveTargetSet(opts) {
|
|
5623
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
5624
|
+
const exts = (opts.exts ?? supportedExtensions()).map(
|
|
5625
|
+
(e) => e.toLowerCase().startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
5626
|
+
);
|
|
5627
|
+
const ignore = [...DEFAULT_IGNORE, ...opts.exclude ?? []];
|
|
5628
|
+
const named = /* @__PURE__ */ new Set();
|
|
5629
|
+
const walked = /* @__PURE__ */ new Set();
|
|
5630
|
+
const keepByExt = (file) => exts.includes(extname(file).toLowerCase());
|
|
5631
|
+
const missing = [];
|
|
5632
|
+
for (const input of opts.inputs) {
|
|
5633
|
+
if (input === STDIN_TOKEN) continue;
|
|
5634
|
+
const posixInput = toPosix(input);
|
|
5635
|
+
const abs = resolve4(cwd, posixInput);
|
|
5636
|
+
const st = await statOrNull(abs);
|
|
5637
|
+
if (st?.isFile()) {
|
|
5638
|
+
named.add(toPosix(relative3(cwd, abs)));
|
|
5639
|
+
continue;
|
|
5640
|
+
}
|
|
5641
|
+
if (st?.isDirectory()) {
|
|
5642
|
+
const found2 = await fg(`${posixInput}/**/*`, {
|
|
5643
|
+
cwd,
|
|
5644
|
+
ignore,
|
|
5645
|
+
onlyFiles: true,
|
|
5646
|
+
dot: false
|
|
5647
|
+
});
|
|
5648
|
+
for (const f of found2) if (keepByExt(f)) walked.add(f);
|
|
5649
|
+
continue;
|
|
5650
|
+
}
|
|
5651
|
+
if (!picomatch2.scan(posixInput).isGlob) {
|
|
5652
|
+
missing.push(posixInput);
|
|
5653
|
+
continue;
|
|
5654
|
+
}
|
|
5655
|
+
const found = await fg(posixInput, {
|
|
5656
|
+
cwd,
|
|
5657
|
+
ignore,
|
|
5658
|
+
onlyFiles: true,
|
|
5659
|
+
dot: false
|
|
5660
|
+
});
|
|
5661
|
+
for (const f of found) if (keepByExt(f)) walked.add(f);
|
|
5237
5662
|
}
|
|
5238
|
-
|
|
5663
|
+
if (missing.length > 0 && !opts.allowEmpty) {
|
|
5664
|
+
const names = missing.map((m) => `"${m}"`).join(", ");
|
|
5665
|
+
throw new DocmetaError(
|
|
5666
|
+
missing.length === 1 ? `File not found: ${names}.` : `Files not found: ${names}.`
|
|
5667
|
+
);
|
|
5668
|
+
}
|
|
5669
|
+
let gitignoreSkipped = 0;
|
|
5670
|
+
if (opts.respectGitignore !== false && walked.size > 0) {
|
|
5671
|
+
const candidates = [...walked].filter((f) => !named.has(f));
|
|
5672
|
+
const { ignored, available } = await gitIgnored(candidates, cwd);
|
|
5673
|
+
if (available) {
|
|
5674
|
+
for (const f of ignored) {
|
|
5675
|
+
if (walked.delete(f)) gitignoreSkipped += 1;
|
|
5676
|
+
}
|
|
5677
|
+
} else {
|
|
5678
|
+
opts.onGitignoreUnavailable?.();
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
return {
|
|
5682
|
+
files: [.../* @__PURE__ */ new Set([...named, ...walked])].sort(),
|
|
5683
|
+
gitignoreSkipped
|
|
5684
|
+
};
|
|
5239
5685
|
}
|
|
5240
|
-
function
|
|
5241
|
-
if (
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5686
|
+
function assertNonEmpty(p) {
|
|
5687
|
+
if (p.allowEmpty || p.usingStdin || p.files.length > 0) return;
|
|
5688
|
+
const tried = p.inputs.map((i) => `"${i}"`).join(", ");
|
|
5689
|
+
const notes = [];
|
|
5690
|
+
if (p.exts && p.exts.length > 0) {
|
|
5691
|
+
notes.push(`extensions: ${p.exts.join(", ")}`);
|
|
5245
5692
|
}
|
|
5246
|
-
|
|
5247
|
-
`
|
|
5693
|
+
if (p.exclude && p.exclude.length > 0) {
|
|
5694
|
+
notes.push(`excludes: ${p.exclude.map((e) => `"${e}"`).join(", ")}`);
|
|
5695
|
+
}
|
|
5696
|
+
if (p.gitignoreSkipped && p.gitignoreSkipped > 0) {
|
|
5697
|
+
notes.push(
|
|
5698
|
+
`.gitignore skipped ${p.gitignoreSkipped} (pass --no-gitignore to check them)`
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
throw new DocmetaError(
|
|
5702
|
+
[
|
|
5703
|
+
`No files matched. Patterns tried: ${tried}.`,
|
|
5704
|
+
...notes.length > 0 ? [`Filters applied \u2014 ${notes.join("; ")}.`] : [],
|
|
5705
|
+
`Nothing was ${p.action}, so this is an error rather than a pass.`,
|
|
5706
|
+
`Pass --allow-empty (or set allowEmpty: true) if matching nothing is expected.`
|
|
5707
|
+
].join("\n")
|
|
5248
5708
|
);
|
|
5249
5709
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5710
|
+
|
|
5711
|
+
// src/meta/core/sidecars.ts
|
|
5712
|
+
var SIDECAR_OWNED_SCHEMA = "sidecar:owned";
|
|
5713
|
+
var SIDECAR_DUPLICATE_SCHEMA = "sidecar:duplicate";
|
|
5714
|
+
var SIDECAR_KEYWORD = "sidecar";
|
|
5715
|
+
var PATH_JOIN = "path";
|
|
5716
|
+
var posix = (p) => p.split(sep2).join("/");
|
|
5717
|
+
function reportedPath(abs, base) {
|
|
5718
|
+
const rel = relative4(base, abs);
|
|
5719
|
+
return rel === "" ? "." : posix(rel);
|
|
5720
|
+
}
|
|
5721
|
+
function sidecarJoin(sidecar) {
|
|
5722
|
+
return sidecar.join ?? PATH_JOIN;
|
|
5723
|
+
}
|
|
5724
|
+
async function loadSidecars(config, opts) {
|
|
5725
|
+
const configured = config?.sidecars;
|
|
5726
|
+
if (!configured || configured.length === 0) return null;
|
|
5727
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5728
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
5729
|
+
const byField = /* @__PURE__ */ new Map();
|
|
5730
|
+
const entries = [];
|
|
5731
|
+
for (const sidecar of configured) {
|
|
5732
|
+
let text;
|
|
5733
|
+
let file;
|
|
5734
|
+
if (classifyRef(sidecar.file).kind === "url") {
|
|
5735
|
+
file = sidecar.file;
|
|
5736
|
+
text = await fetchSidecar(sidecar.file, {
|
|
5737
|
+
...sidecar.tokenEnv !== void 0 ? { tokenEnv: sidecar.tokenEnv } : {},
|
|
5738
|
+
...opts.offline !== void 0 ? { offline: opts.offline } : {},
|
|
5739
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
5740
|
+
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
5741
|
+
});
|
|
5742
|
+
} else {
|
|
5743
|
+
const abs = isAbsolute2(sidecar.file) ? sidecar.file : resolve5(opts.configDir, sidecar.file);
|
|
5744
|
+
file = reportedPath(abs, opts.base);
|
|
5745
|
+
text = await readManifest(abs, file);
|
|
5746
|
+
}
|
|
5747
|
+
for (const key of sidecar.keys) owners.set(key, file);
|
|
5748
|
+
const join7 = sidecarJoin(sidecar);
|
|
5749
|
+
let target;
|
|
5750
|
+
if (join7 === PATH_JOIN) {
|
|
5751
|
+
target = byPath;
|
|
5752
|
+
} else {
|
|
5753
|
+
const existing = byField.get(join7);
|
|
5754
|
+
if (existing) target = existing;
|
|
5755
|
+
else {
|
|
5756
|
+
target = /* @__PURE__ */ new Map();
|
|
5757
|
+
byField.set(join7, target);
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
parseManifest(sidecar, text, file, opts.configDir, join7, target, entries);
|
|
5761
|
+
}
|
|
5762
|
+
return { owners, byPath, byField, entries };
|
|
5252
5763
|
}
|
|
5253
|
-
function
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
const
|
|
5258
|
-
|
|
5259
|
-
|
|
5764
|
+
async function readManifest(abs, file) {
|
|
5765
|
+
try {
|
|
5766
|
+
return await readFile3(abs, "utf8");
|
|
5767
|
+
} catch (err) {
|
|
5768
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5769
|
+
throw new DocmetaError(
|
|
5770
|
+
`Sidecar manifest ${file} could not be read: ${reason}`
|
|
5771
|
+
);
|
|
5772
|
+
}
|
|
5260
5773
|
}
|
|
5261
|
-
function
|
|
5262
|
-
const
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5774
|
+
function parseManifest(sidecar, text, file, configDir, join7, target, entries) {
|
|
5775
|
+
const lc = new LineCounter2();
|
|
5776
|
+
const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
|
|
5777
|
+
const problem = doc.errors[0];
|
|
5778
|
+
if (problem) {
|
|
5779
|
+
throw new DocmetaError(
|
|
5780
|
+
`Sidecar manifest ${file} is not valid YAML: ${problem.message}`
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5783
|
+
const root = doc.contents;
|
|
5784
|
+
if (root === null || isScalar2(root) && root.value === null) return;
|
|
5785
|
+
if (!isMap3(root)) {
|
|
5786
|
+
throw new DocmetaError(
|
|
5787
|
+
`Sidecar manifest ${file}: the manifest must be a mapping from document ${join7 === PATH_JOIN ? "path" : `"${join7}"`} to owned keys.`
|
|
5788
|
+
);
|
|
5789
|
+
}
|
|
5790
|
+
const owned = new Set(sidecar.keys);
|
|
5791
|
+
const lineAt = (node) => {
|
|
5792
|
+
const range = isNode(node) ? node.range : void 0;
|
|
5793
|
+
return range ? lc.linePos(range[0]).line : void 0;
|
|
5794
|
+
};
|
|
5795
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5796
|
+
for (const pair of root.items) {
|
|
5797
|
+
const spelled = isScalar2(pair.key) ? String(pair.key.value) : String(pair.key);
|
|
5798
|
+
const entryLine = lineAt(pair.key);
|
|
5799
|
+
const where = entryLine === void 0 ? file : `${file}:${entryLine}`;
|
|
5800
|
+
if (seen.has(spelled)) {
|
|
5801
|
+
const first = seen.get(spelled);
|
|
5266
5802
|
throw new DocmetaError(
|
|
5267
|
-
`
|
|
5803
|
+
`Sidecar manifest ${where}: "${spelled}" is named twice (first at line ${first ?? "?"}). Merge the two entries into one.`
|
|
5268
5804
|
);
|
|
5269
5805
|
}
|
|
5270
|
-
|
|
5271
|
-
if (!
|
|
5272
|
-
let url;
|
|
5273
|
-
try {
|
|
5274
|
-
url = new URL(ref);
|
|
5275
|
-
} catch {
|
|
5806
|
+
seen.set(spelled, entryLine);
|
|
5807
|
+
if (!isMap3(pair.value)) {
|
|
5276
5808
|
throw new DocmetaError(
|
|
5277
|
-
`
|
|
5809
|
+
`Sidecar manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
|
|
5278
5810
|
);
|
|
5279
5811
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
);
|
|
5812
|
+
const indexKey = join7 === PATH_JOIN ? resolve5(configDir, spelled) : spelled;
|
|
5813
|
+
const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
|
|
5814
|
+
for (const kv of pair.value.items) {
|
|
5815
|
+
const key = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
|
|
5816
|
+
if (key === FILE_SCHEMA_KEY) {
|
|
5817
|
+
throw new DocmetaError(
|
|
5818
|
+
`Sidecar manifest ${where}: "${spelled}" sets "${FILE_SCHEMA_KEY}" \u2014 a sidecar never chooses the schema a document is judged by; use "overrides" in the config.`
|
|
5819
|
+
);
|
|
5820
|
+
}
|
|
5821
|
+
if (!owned.has(key)) {
|
|
5822
|
+
throw new DocmetaError(
|
|
5823
|
+
`Sidecar manifest ${where}: "${spelled}" sets "${key}", which this sidecar does not own. Add it to the sidecar's "keys", or remove it from the entry.`
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5826
|
+
const value = kv.value === null ? null : kv.value.toJS(doc, { maxAliasCount: 100 });
|
|
5827
|
+
const line = lineAt(kv.key);
|
|
5828
|
+
values.set(key, { value, file, ...line === void 0 ? {} : { line } });
|
|
5284
5829
|
}
|
|
5285
|
-
|
|
5830
|
+
target.set(indexKey, values);
|
|
5831
|
+
entries.push({
|
|
5832
|
+
join: join7,
|
|
5833
|
+
...join7 === PATH_JOIN ? { abs: indexKey } : {},
|
|
5834
|
+
spelled,
|
|
5835
|
+
file,
|
|
5836
|
+
...entryLine === void 0 ? {} : { line: entryLine }
|
|
5837
|
+
});
|
|
5286
5838
|
}
|
|
5287
|
-
const root = params.trustRoot;
|
|
5288
|
-
if (!root) return;
|
|
5289
|
-
const abs = resolve(params.fileBase ?? process.cwd(), ref);
|
|
5290
|
-
const within = relative(root.dir, abs);
|
|
5291
|
-
if (within !== "" && !within.startsWith("..") && !isAbsolute(within)) return;
|
|
5292
|
-
const boundary = root.source === "git" ? `the repository root (${root.dir})` : root.source === "config" ? `${root.dir} \u2014 no git repository was found, so the config file's own directory is the boundary` : `${root.dir} \u2014 no git repository and no config file were found, so the directory the command was run from is the boundary`;
|
|
5293
|
-
const scope = root.source === "git" ? "the repository" : "the project";
|
|
5294
|
-
throw new DocmetaError(
|
|
5295
|
-
`Refusing the "${FILE_SCHEMA_KEY}" path "${ref}": it resolves outside ${boundary}. A document may only name a schema inside ${scope}; a path in \`schemas:\` or on \`--schema\` is not restricted this way.`
|
|
5296
|
-
);
|
|
5297
5839
|
}
|
|
5298
|
-
function
|
|
5299
|
-
|
|
5300
|
-
if (
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5840
|
+
function joinValue(raw) {
|
|
5841
|
+
if (typeof raw === "string") return raw;
|
|
5842
|
+
if (typeof raw === "number" || typeof raw === "bigint") return String(raw);
|
|
5843
|
+
if (typeof raw === "boolean") return String(raw);
|
|
5844
|
+
return void 0;
|
|
5845
|
+
}
|
|
5846
|
+
function mergeSidecars(label, extracted, index, base) {
|
|
5847
|
+
const none = () => void 0;
|
|
5848
|
+
if (!index) return { extracted, collisions: [], joins: [], locate: none };
|
|
5849
|
+
const collisions = [];
|
|
5850
|
+
for (const key of Object.keys(extracted.data)) {
|
|
5851
|
+
const file = index.owners.get(key);
|
|
5852
|
+
if (file !== void 0) collisions.push({ key, file });
|
|
5853
|
+
}
|
|
5854
|
+
const sources = [];
|
|
5855
|
+
const joins = [];
|
|
5856
|
+
if (label !== STDIN_LABEL) {
|
|
5857
|
+
const byPath = index.byPath.get(resolve5(base, label));
|
|
5858
|
+
if (byPath) sources.push(byPath);
|
|
5859
|
+
}
|
|
5860
|
+
for (const [field, byValue] of index.byField) {
|
|
5861
|
+
const value = joinValue(extracted.data[field]);
|
|
5862
|
+
if (value === void 0) continue;
|
|
5863
|
+
const hit = byValue.get(value);
|
|
5864
|
+
if (!hit) continue;
|
|
5865
|
+
sources.push(hit);
|
|
5866
|
+
const first = hit.values().next().value;
|
|
5867
|
+
joins.push({ field, value, file: first?.file ?? "manifest" });
|
|
5321
5868
|
}
|
|
5322
|
-
if (
|
|
5323
|
-
return {
|
|
5324
|
-
schemas: dedupe(config.schemas.map(schemaEntryRef)),
|
|
5325
|
-
source: "config"
|
|
5326
|
-
};
|
|
5869
|
+
if (sources.length === 0) {
|
|
5870
|
+
return { extracted, collisions, joins, locate: none };
|
|
5327
5871
|
}
|
|
5328
|
-
|
|
5872
|
+
const data = { ...extracted.data };
|
|
5873
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5874
|
+
for (const supplied of sources) {
|
|
5875
|
+
for (const [key, sv] of supplied) {
|
|
5876
|
+
if (key in extracted.data) continue;
|
|
5877
|
+
data[key] = sv.value;
|
|
5878
|
+
merged.set(key, sv);
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
const locate = (pointer) => {
|
|
5882
|
+
const top = topLevelKey(pointer);
|
|
5883
|
+
if (top === void 0) return void 0;
|
|
5884
|
+
const sv = merged.get(top);
|
|
5885
|
+
if (!sv) return void 0;
|
|
5886
|
+
return { file: sv.file, ...sv.line === void 0 ? {} : { line: sv.line } };
|
|
5887
|
+
};
|
|
5888
|
+
return { extracted: { ...extracted, data }, collisions, joins, locate };
|
|
5329
5889
|
}
|
|
5330
|
-
function
|
|
5331
|
-
|
|
5890
|
+
function topLevelKey(pointer) {
|
|
5891
|
+
if (pointer === "") return void 0;
|
|
5892
|
+
if (!pointer.startsWith("/")) return pointer;
|
|
5893
|
+
const seg = pointer.slice(1).split("/")[0] ?? "";
|
|
5894
|
+
return seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
5332
5895
|
}
|
|
5333
|
-
function
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
return
|
|
5896
|
+
function sidecarPointer(key) {
|
|
5897
|
+
return `/${escapePointerSegment(key)}`;
|
|
5898
|
+
}
|
|
5899
|
+
function orphanEntries(index, loaded, base) {
|
|
5900
|
+
if (!index) return [];
|
|
5901
|
+
const have = new Set(loaded.map((l) => resolve5(base, l)));
|
|
5902
|
+
return index.entries.filter(
|
|
5903
|
+
(e) => e.join === PATH_JOIN && e.abs !== void 0 && !have.has(e.abs)
|
|
5904
|
+
);
|
|
5905
|
+
}
|
|
5906
|
+
function orphanJoins(index, matched) {
|
|
5907
|
+
if (!index) return [];
|
|
5908
|
+
return index.entries.filter(
|
|
5909
|
+
(e) => e.join !== PATH_JOIN && !(matched.get(e.join)?.has(e.spelled) ?? false)
|
|
5910
|
+
);
|
|
5911
|
+
}
|
|
5912
|
+
function orphanError(orphans) {
|
|
5913
|
+
const first = orphans[0];
|
|
5914
|
+
if (!first) throw new Error("orphanError called with no orphans");
|
|
5915
|
+
const where = first.line === void 0 ? first.file : `${first.file}:${first.line}`;
|
|
5916
|
+
const more = orphans.length > 1 ? ` (${orphans.length - 1} more)` : "";
|
|
5917
|
+
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
|
+
return new DocmetaError(
|
|
5919
|
+
`Sidecar manifest ${where} ${what}${more}. Fix the entry, or remove it.`
|
|
5920
|
+
);
|
|
5340
5921
|
}
|
|
5341
5922
|
|
|
5342
5923
|
// src/meta/core/config.ts
|
|
5343
|
-
import { resolve as
|
|
5924
|
+
import { resolve as resolve7 } from "node:path";
|
|
5344
5925
|
import { parse as parseYaml2 } from "yaml";
|
|
5345
5926
|
|
|
5346
5927
|
// src/shared/config-file.ts
|
|
5347
|
-
import { readFile as
|
|
5348
|
-
import { dirname as
|
|
5928
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
5929
|
+
import { dirname as dirname4, join as join4, relative as relative5, resolve as resolve6 } from "node:path";
|
|
5349
5930
|
import { parse as parseYaml } from "yaml";
|
|
5350
5931
|
|
|
5351
|
-
// src/shared/git-root.ts
|
|
5352
|
-
import { existsSync } from "node:fs";
|
|
5353
|
-
import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
5354
|
-
function findGitRoot(cwd) {
|
|
5355
|
-
let dir = resolve2(cwd);
|
|
5356
|
-
for (; ; ) {
|
|
5357
|
-
if (existsSync(join3(dir, ".git"))) return dir;
|
|
5358
|
-
const parent = dirname2(dir);
|
|
5359
|
-
if (parent === dir) return null;
|
|
5360
|
-
dir = parent;
|
|
5361
|
-
}
|
|
5362
|
-
}
|
|
5363
|
-
function searchPath(cwd) {
|
|
5364
|
-
const start = resolve2(cwd);
|
|
5365
|
-
const root = findGitRoot(start);
|
|
5366
|
-
if (root === null) return [start];
|
|
5367
|
-
const chain = [];
|
|
5368
|
-
let dir = start;
|
|
5369
|
-
for (; ; ) {
|
|
5370
|
-
chain.push(dir);
|
|
5371
|
-
if (dir === root) return chain;
|
|
5372
|
-
const parent = dirname2(dir);
|
|
5373
|
-
if (parent === dir) return chain;
|
|
5374
|
-
dir = parent;
|
|
5375
|
-
}
|
|
5376
|
-
}
|
|
5377
|
-
|
|
5378
5932
|
// src/shared/warn.ts
|
|
5379
5933
|
var said = /* @__PURE__ */ new Set();
|
|
5380
5934
|
function warn(message) {
|
|
@@ -5396,7 +5950,7 @@ var MOOSE_CONFIG_NAMES = [
|
|
|
5396
5950
|
async function readDocument(path, source, opts) {
|
|
5397
5951
|
let text;
|
|
5398
5952
|
try {
|
|
5399
|
-
text = await
|
|
5953
|
+
text = await readFile4(path, "utf8");
|
|
5400
5954
|
} catch {
|
|
5401
5955
|
return null;
|
|
5402
5956
|
}
|
|
@@ -5423,10 +5977,10 @@ function slice(document, opts) {
|
|
|
5423
5977
|
return null;
|
|
5424
5978
|
}
|
|
5425
5979
|
function relativeSource(cwd, path) {
|
|
5426
|
-
return
|
|
5980
|
+
return relative5(cwd, path).replace(/\\/g, "/");
|
|
5427
5981
|
}
|
|
5428
5982
|
async function findConfigFile(cwd, opts) {
|
|
5429
|
-
const start =
|
|
5983
|
+
const start = resolve6(cwd);
|
|
5430
5984
|
for (const dir of searchPath(start)) {
|
|
5431
5985
|
const family = [
|
|
5432
5986
|
{ names: FAMILY_CONFIG_NAMES, kind: "manni" },
|
|
@@ -5470,7 +6024,7 @@ async function findConfigFile(cwd, opts) {
|
|
|
5470
6024
|
return null;
|
|
5471
6025
|
}
|
|
5472
6026
|
async function readConfigFile(explicitPath, cwd, opts) {
|
|
5473
|
-
const path =
|
|
6027
|
+
const path = resolve6(cwd, explicitPath);
|
|
5474
6028
|
const document = await readDocument(path, explicitPath, opts);
|
|
5475
6029
|
if (document === null) {
|
|
5476
6030
|
throw opts.toError(`Config file not found: "${explicitPath}".`);
|
|
@@ -5481,7 +6035,7 @@ async function readConfigFile(explicitPath, cwd, opts) {
|
|
|
5481
6035
|
};
|
|
5482
6036
|
return {
|
|
5483
6037
|
path,
|
|
5484
|
-
dir:
|
|
6038
|
+
dir: dirname4(path),
|
|
5485
6039
|
source: explicitPath,
|
|
5486
6040
|
text: document.text,
|
|
5487
6041
|
kind: "explicit",
|
|
@@ -5507,6 +6061,7 @@ function asElementPaths(value, where, source) {
|
|
|
5507
6061
|
}
|
|
5508
6062
|
var OVERRIDE_KEYS = ["name", "files", "schemas", "elements"];
|
|
5509
6063
|
var CHECK_KEYS = ["name", "query"];
|
|
6064
|
+
var SIDECAR_KEYS = ["file", "keys", "tokenEnv", "join"];
|
|
5510
6065
|
var CHECK_NAME = /^[a-z0-9][a-z0-9._-]*$/;
|
|
5511
6066
|
var RESERVED_CHECK_NAMES = /* @__PURE__ */ new Set(["query"]);
|
|
5512
6067
|
var FILL_KEYS = [
|
|
@@ -5523,6 +6078,7 @@ var CONFIG_KEYS = [
|
|
|
5523
6078
|
"schemas",
|
|
5524
6079
|
"overrides",
|
|
5525
6080
|
"checks",
|
|
6081
|
+
"sidecars",
|
|
5526
6082
|
"baseline",
|
|
5527
6083
|
"allowEmpty",
|
|
5528
6084
|
"respectGitignore",
|
|
@@ -5666,6 +6222,93 @@ function withSection(message, source, section) {
|
|
|
5666
6222
|
if (/^[a-zA-Z]+(\[|\.| —)/.test(rest)) return `${head}${section}.${rest}`;
|
|
5667
6223
|
return message;
|
|
5668
6224
|
}
|
|
6225
|
+
function parseSidecars(raw, source) {
|
|
6226
|
+
if (!Array.isArray(raw)) {
|
|
6227
|
+
throw new DocmetaError(`${source}: "sidecars" must be a list.`);
|
|
6228
|
+
}
|
|
6229
|
+
const owners = /* @__PURE__ */ new Map();
|
|
6230
|
+
return raw.map((entry, i) => {
|
|
6231
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
6232
|
+
throw new DocmetaError(`${source}: sidecars[${i}] must be a mapping.`);
|
|
6233
|
+
}
|
|
6234
|
+
const e = entry;
|
|
6235
|
+
rejectUnknownKeys(e, SIDECAR_KEYS, `sidecars[${i}]`, source);
|
|
6236
|
+
if (typeof e.file !== "string" || e.file.trim() === "") {
|
|
6237
|
+
throw new DocmetaError(
|
|
6238
|
+
`${source}: sidecars[${i}].file must be a non-empty string naming the manifest, relative to the config file.`
|
|
6239
|
+
);
|
|
6240
|
+
}
|
|
6241
|
+
if (!Array.isArray(e.keys) || e.keys.length === 0 || e.keys.some((k) => typeof k !== "string" || k.trim() === "")) {
|
|
6242
|
+
throw new DocmetaError(
|
|
6243
|
+
`${source}: sidecars[${i}].keys must be a non-empty list of key names.`
|
|
6244
|
+
);
|
|
6245
|
+
}
|
|
6246
|
+
const isUrl = classifyRef(e.file).kind === "url";
|
|
6247
|
+
if (isUrl) {
|
|
6248
|
+
const problem = sidecarUrlProblem(e.file);
|
|
6249
|
+
if (problem !== null) {
|
|
6250
|
+
throw new DocmetaError(`${source}: sidecars[${i}].file ${problem}.`);
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6253
|
+
if (e.tokenEnv !== void 0) {
|
|
6254
|
+
if (typeof e.tokenEnv !== "string" || e.tokenEnv.trim() === "") {
|
|
6255
|
+
throw new DocmetaError(
|
|
6256
|
+
`${source}: sidecars[${i}].tokenEnv must be the name of an environment variable.`
|
|
6257
|
+
);
|
|
6258
|
+
}
|
|
6259
|
+
if (!isUrl) {
|
|
6260
|
+
throw new DocmetaError(
|
|
6261
|
+
`${source}: sidecars[${i}].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.`
|
|
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
|
+
});
|
|
6311
|
+
}
|
|
5669
6312
|
function parseConfigDocument(raw, source) {
|
|
5670
6313
|
if (raw == null) return {};
|
|
5671
6314
|
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -5739,6 +6382,9 @@ function parseConfigDocument(raw, source) {
|
|
|
5739
6382
|
if (obj.checks !== void 0) {
|
|
5740
6383
|
config.checks = parseChecks(obj.checks, source);
|
|
5741
6384
|
}
|
|
6385
|
+
if (obj.sidecars !== void 0) {
|
|
6386
|
+
config.sidecars = parseSidecars(obj.sidecars, source);
|
|
6387
|
+
}
|
|
5742
6388
|
if (obj.elements !== void 0) {
|
|
5743
6389
|
config.elements = asElementPaths(obj.elements, "elements", source);
|
|
5744
6390
|
}
|
|
@@ -5907,8 +6553,8 @@ function parseFill(value, source) {
|
|
|
5907
6553
|
function schemaTrustRoot(cwd, configDir) {
|
|
5908
6554
|
const git = findGitRoot(cwd);
|
|
5909
6555
|
if (git !== null) return { dir: git, source: "git" };
|
|
5910
|
-
if (configDir !== void 0) return { dir:
|
|
5911
|
-
return { dir:
|
|
6556
|
+
if (configDir !== void 0) return { dir: resolve7(configDir), source: "config" };
|
|
6557
|
+
return { dir: resolve7(cwd), source: "cwd" };
|
|
5912
6558
|
}
|
|
5913
6559
|
async function loadConfig(explicitPath, cwd = process.cwd()) {
|
|
5914
6560
|
const file = explicitPath ? await readConfigFile(explicitPath, cwd, CONFIG_FILE) : await findConfigFile(cwd, CONFIG_FILE);
|
|
@@ -6089,7 +6735,7 @@ var Validator = class {
|
|
|
6089
6735
|
* Callers with an extractor that supplies no column pass nothing and get the
|
|
6090
6736
|
* previous behavior exactly.
|
|
6091
6737
|
*/
|
|
6092
|
-
async validate(data, refs, lineFor, colFor) {
|
|
6738
|
+
async validate(data, refs, lineFor, colFor, locate) {
|
|
6093
6739
|
const { [FILE_SCHEMA_KEY]: _omit, ...subject } = data;
|
|
6094
6740
|
void _omit;
|
|
6095
6741
|
const errors = [];
|
|
@@ -6098,7 +6744,7 @@ var Validator = class {
|
|
|
6098
6744
|
const ok = fn(subject);
|
|
6099
6745
|
if (ok) continue;
|
|
6100
6746
|
for (const e of fn.errors ?? []) {
|
|
6101
|
-
errors.push(toFieldError(ref, e, lineFor, colFor));
|
|
6747
|
+
errors.push(toFieldError(ref, e, lineFor, colFor, locate));
|
|
6102
6748
|
}
|
|
6103
6749
|
}
|
|
6104
6750
|
return errors;
|
|
@@ -6118,7 +6764,7 @@ function subjectOf(e) {
|
|
|
6118
6764
|
return void 0;
|
|
6119
6765
|
}
|
|
6120
6766
|
}
|
|
6121
|
-
function toFieldError(schema, e, lineFor, colFor) {
|
|
6767
|
+
function toFieldError(schema, e, lineFor, colFor, locate) {
|
|
6122
6768
|
const defined = e;
|
|
6123
6769
|
const instancePath = e.instancePath;
|
|
6124
6770
|
const subject = subjectOf(defined);
|
|
@@ -6131,6 +6777,21 @@ function toFieldError(schema, e, lineFor, colFor) {
|
|
|
6131
6777
|
message = `must NOT have additional property '${defined.params.additionalProperty}'`;
|
|
6132
6778
|
wantsColumn = false;
|
|
6133
6779
|
}
|
|
6780
|
+
const located = locate?.(instancePath) ?? (defined.keyword === "additionalProperties" ? locate?.(
|
|
6781
|
+
`${instancePath}/${escapePointerSegment(defined.params.additionalProperty)}`
|
|
6782
|
+
) : void 0);
|
|
6783
|
+
if (located) {
|
|
6784
|
+
return {
|
|
6785
|
+
schema,
|
|
6786
|
+
instancePath,
|
|
6787
|
+
message,
|
|
6788
|
+
keyword: e.keyword,
|
|
6789
|
+
...subject != null ? { subject } : {},
|
|
6790
|
+
file: located.file,
|
|
6791
|
+
...located.line != null ? { line: located.line } : {},
|
|
6792
|
+
...wantsColumn && located.col != null ? { col: located.col } : {}
|
|
6793
|
+
};
|
|
6794
|
+
}
|
|
6134
6795
|
const line = lineFor(instancePath);
|
|
6135
6796
|
const col = wantsColumn ? colFor?.(instancePath) : void 0;
|
|
6136
6797
|
return {
|
|
@@ -6146,13 +6807,13 @@ function toFieldError(schema, e, lineFor, colFor) {
|
|
|
6146
6807
|
|
|
6147
6808
|
// src/meta/commands/validate.ts
|
|
6148
6809
|
import { existsSync as existsSync2 } from "node:fs";
|
|
6149
|
-
import { readFile as
|
|
6150
|
-
import { resolve as
|
|
6810
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
6811
|
+
import { resolve as resolve9, extname as extname2 } from "node:path";
|
|
6151
6812
|
|
|
6152
6813
|
// src/meta/core/baseline.ts
|
|
6153
6814
|
import { createHash as createHash3 } from "node:crypto";
|
|
6154
|
-
import { readFile as
|
|
6155
|
-
import { relative as
|
|
6815
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
6816
|
+
import { relative as relative6, resolve as resolve8 } from "node:path";
|
|
6156
6817
|
var DEFAULT_BASELINE_PATH = ".manni-baseline.json";
|
|
6157
6818
|
var LEGACY_BASELINE_PATH = ".docmeta-baseline.json";
|
|
6158
6819
|
var BASELINE_VERSION = 1;
|
|
@@ -6160,13 +6821,13 @@ var FINGERPRINT_RE = /^[0-9a-f]{16}$/;
|
|
|
6160
6821
|
function canonicalFilePath(file, ctx) {
|
|
6161
6822
|
if (!ctx) return file;
|
|
6162
6823
|
const from = ctx.runBase ?? ctx.base;
|
|
6163
|
-
return
|
|
6824
|
+
return relative6(ctx.base, resolve8(from, file)).replace(/\\/g, "/");
|
|
6164
6825
|
}
|
|
6165
6826
|
function canonicalSchemaRef(ref, ctx) {
|
|
6166
6827
|
if (!ctx) return ref;
|
|
6167
6828
|
if (classifyRef(ref).kind !== "file") return ref;
|
|
6168
|
-
const abs =
|
|
6169
|
-
return
|
|
6829
|
+
const abs = resolve8(ctx.cwd, ref);
|
|
6830
|
+
return relative6(ctx.base, abs).replace(/\\/g, "/");
|
|
6170
6831
|
}
|
|
6171
6832
|
function fingerprint(e, ctx) {
|
|
6172
6833
|
const parts = [
|
|
@@ -6195,303 +6856,129 @@ function serializeBaseline(baseline) {
|
|
|
6195
6856
|
}
|
|
6196
6857
|
function bad(source, detail) {
|
|
6197
6858
|
throw new DocmetaError(`Baseline "${source}": ${detail}`);
|
|
6198
|
-
}
|
|
6199
|
-
function parseBaseline(text, source) {
|
|
6200
|
-
let raw;
|
|
6201
|
-
try {
|
|
6202
|
-
raw = JSON.parse(stripBom(text));
|
|
6203
|
-
} catch (err) {
|
|
6204
|
-
bad(source, `invalid JSON: ${err.message}`);
|
|
6205
|
-
}
|
|
6206
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
6207
|
-
bad(source, "top level must be an object.");
|
|
6208
|
-
}
|
|
6209
|
-
const obj = raw;
|
|
6210
|
-
if (obj.version !== BASELINE_VERSION) {
|
|
6211
|
-
bad(
|
|
6212
|
-
source,
|
|
6213
|
-
`unsupported version ${JSON.stringify(obj.version)} (this manni writes version ${BASELINE_VERSION}). Re-record it with \`manni meta validate --write-baseline\`.`
|
|
6214
|
-
);
|
|
6215
|
-
}
|
|
6216
|
-
const generatedWith = typeof obj.generatedWith === "string" ? obj.generatedWith : "";
|
|
6217
|
-
const rawEntries = obj.entries;
|
|
6218
|
-
if (typeof rawEntries !== "object" || rawEntries === null || Array.isArray(rawEntries)) {
|
|
6219
|
-
bad(source, '"entries" must be an object mapping file paths to fingerprints.');
|
|
6220
|
-
}
|
|
6221
|
-
const entries = /* @__PURE__ */ Object.create(null);
|
|
6222
|
-
for (const [file, value] of Object.entries(
|
|
6223
|
-
rawEntries
|
|
6224
|
-
)) {
|
|
6225
|
-
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
6226
|
-
bad(source, `entries["${file}"] must be a list of fingerprint strings.`);
|
|
6227
|
-
}
|
|
6228
|
-
const malformed = value.find((v) => !FINGERPRINT_RE.test(v));
|
|
6229
|
-
if (malformed !== void 0) {
|
|
6230
|
-
bad(
|
|
6231
|
-
source,
|
|
6232
|
-
`entries["${file}"] contains ${JSON.stringify(malformed)}, which is not a fingerprint (16 lowercase hex characters).`
|
|
6233
|
-
);
|
|
6234
|
-
}
|
|
6235
|
-
entries[file] = value;
|
|
6236
|
-
}
|
|
6237
|
-
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
6238
|
-
}
|
|
6239
|
-
async function readBaseline(absPath, source) {
|
|
6240
|
-
let text;
|
|
6241
|
-
try {
|
|
6242
|
-
text = await readFile4(absPath, "utf8");
|
|
6243
|
-
} catch (err) {
|
|
6244
|
-
if (err.code === "ENOENT") return null;
|
|
6245
|
-
throw new DocmetaError(
|
|
6246
|
-
`Baseline "${source}" could not be read: ${err.message}`
|
|
6247
|
-
);
|
|
6248
|
-
}
|
|
6249
|
-
return parseBaseline(text, source);
|
|
6250
|
-
}
|
|
6251
|
-
async function writeBaselineFile(absPath, baseline, source) {
|
|
6252
|
-
try {
|
|
6253
|
-
await writeFileAtomic(absPath, serializeBaseline(baseline));
|
|
6254
|
-
} catch (err) {
|
|
6255
|
-
throw new DocmetaError(
|
|
6256
|
-
`Baseline "${source}" could not be written: ${err.message}`
|
|
6257
|
-
);
|
|
6258
|
-
}
|
|
6259
|
-
}
|
|
6260
|
-
function buildBaseline(results, generatedWith, ctx) {
|
|
6261
|
-
const entries = /* @__PURE__ */ Object.create(null);
|
|
6262
|
-
for (const r of results) {
|
|
6263
|
-
if (r.errors.length === 0) continue;
|
|
6264
|
-
const prints = [...new Set(r.errors.map((e) => fingerprint(e, ctx)))].sort();
|
|
6265
|
-
entries[canonicalFilePath(r.file, ctx)] = prints;
|
|
6266
|
-
}
|
|
6267
|
-
return { version: BASELINE_VERSION, generatedWith, entries };
|
|
6268
|
-
}
|
|
6269
|
-
function applyBaseline(results, baseline, ctx) {
|
|
6270
|
-
let recorded = 0;
|
|
6271
|
-
let suppressed = 0;
|
|
6272
|
-
let stale = 0;
|
|
6273
|
-
const applied = results.map((r) => {
|
|
6274
|
-
const known = baseline.entries[canonicalFilePath(r.file, ctx)];
|
|
6275
|
-
if (!known) return r;
|
|
6276
|
-
const recordedHere = new Set(known);
|
|
6277
|
-
recorded += recordedHere.size;
|
|
6278
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6279
|
-
const fresh = [];
|
|
6280
|
-
for (const e of r.errors) {
|
|
6281
|
-
const print = fingerprint(e, ctx);
|
|
6282
|
-
if (recordedHere.has(print)) {
|
|
6283
|
-
seen.add(print);
|
|
6284
|
-
suppressed += 1;
|
|
6285
|
-
} else {
|
|
6286
|
-
fresh.push(e);
|
|
6287
|
-
}
|
|
6288
|
-
}
|
|
6289
|
-
stale += recordedHere.size - seen.size;
|
|
6290
|
-
const baselined = r.errors.length - fresh.length;
|
|
6291
|
-
return {
|
|
6292
|
-
...r,
|
|
6293
|
-
ok: fresh.length === 0,
|
|
6294
|
-
errors: fresh,
|
|
6295
|
-
...baselined > 0 ? { baselined } : {}
|
|
6296
|
-
};
|
|
6297
|
-
});
|
|
6298
|
-
return { results: applied, recorded, suppressed, stale };
|
|
6299
|
-
}
|
|
6300
|
-
function diffBaselines(previous, next) {
|
|
6301
|
-
const flatten = (b) => {
|
|
6302
|
-
const out = /* @__PURE__ */ new Set();
|
|
6303
|
-
if (!b) return out;
|
|
6304
|
-
for (const [file, prints] of Object.entries(b.entries)) {
|
|
6305
|
-
for (const p of prints) out.add(`${file}\0${p}`);
|
|
6306
|
-
}
|
|
6307
|
-
return out;
|
|
6308
|
-
};
|
|
6309
|
-
const before = flatten(previous);
|
|
6310
|
-
const after = flatten(next);
|
|
6311
|
-
let added = 0;
|
|
6312
|
-
let removed = 0;
|
|
6313
|
-
for (const k of after) if (!before.has(k)) added += 1;
|
|
6314
|
-
for (const k of before) if (!after.has(k)) removed += 1;
|
|
6315
|
-
return { added, removed };
|
|
6316
|
-
}
|
|
6317
|
-
function countFingerprints(baseline) {
|
|
6318
|
-
let n = 0;
|
|
6319
|
-
for (const prints of Object.values(baseline.entries)) n += prints.length;
|
|
6320
|
-
return n;
|
|
6321
|
-
}
|
|
6322
|
-
|
|
6323
|
-
// src/meta/core/load-files.ts
|
|
6324
|
-
import { stat as stat3 } from "node:fs/promises";
|
|
6325
|
-
import { extname, relative as relative4, resolve as resolve6 } from "node:path";
|
|
6326
|
-
import fg from "fast-glob";
|
|
6327
|
-
import picomatch2 from "picomatch";
|
|
6328
|
-
|
|
6329
|
-
// src/meta/core/gitignore.ts
|
|
6330
|
-
import { spawn } from "node:child_process";
|
|
6331
|
-
var GITIGNORE_UNAVAILABLE = ".gitignore filtering was requested but git could not answer here (no repository, or no git on PATH); no files were skipped.";
|
|
6332
|
-
var unavailable = () => ({
|
|
6333
|
-
ignored: /* @__PURE__ */ new Set(),
|
|
6334
|
-
available: false
|
|
6335
|
-
});
|
|
6336
|
-
function gitIgnored(paths, cwd) {
|
|
6337
|
-
if (paths.length === 0) {
|
|
6338
|
-
return Promise.resolve({ ignored: /* @__PURE__ */ new Set(), available: true });
|
|
6339
|
-
}
|
|
6340
|
-
return new Promise((settle) => {
|
|
6341
|
-
let done = false;
|
|
6342
|
-
const finish = (answer) => {
|
|
6343
|
-
if (done) return;
|
|
6344
|
-
done = true;
|
|
6345
|
-
settle(answer);
|
|
6346
|
-
};
|
|
6347
|
-
let child;
|
|
6348
|
-
try {
|
|
6349
|
-
child = spawn("git", ["check-ignore", "--stdin", "-z"], {
|
|
6350
|
-
cwd,
|
|
6351
|
-
windowsHide: true
|
|
6352
|
-
});
|
|
6353
|
-
} catch {
|
|
6354
|
-
finish(unavailable());
|
|
6355
|
-
return;
|
|
6356
|
-
}
|
|
6357
|
-
let out = "";
|
|
6358
|
-
child.stdout.setEncoding("utf8");
|
|
6359
|
-
child.stdout.on("data", (chunk) => {
|
|
6360
|
-
out += chunk;
|
|
6361
|
-
});
|
|
6362
|
-
child.stderr.resume();
|
|
6363
|
-
child.on("error", () => {
|
|
6364
|
-
finish(unavailable());
|
|
6365
|
-
});
|
|
6366
|
-
child.stdin.on("error", () => {
|
|
6367
|
-
});
|
|
6368
|
-
child.on("close", (code) => {
|
|
6369
|
-
if (code !== 0 && code !== 1) {
|
|
6370
|
-
finish(unavailable());
|
|
6371
|
-
return;
|
|
6372
|
-
}
|
|
6373
|
-
finish({
|
|
6374
|
-
ignored: new Set(out.split("\0").filter((p) => p !== "")),
|
|
6375
|
-
available: true
|
|
6376
|
-
});
|
|
6377
|
-
});
|
|
6378
|
-
child.stdin.end(paths.join("\0"));
|
|
6379
|
-
});
|
|
6380
|
-
}
|
|
6381
|
-
|
|
6382
|
-
// src/meta/core/load-files.ts
|
|
6383
|
-
var DEFAULT_IGNORE = ["**/node_modules/**", "**/.git/**"];
|
|
6384
|
-
var STDIN_TOKEN = "-";
|
|
6385
|
-
var STDIN_LABEL = "<stdin>";
|
|
6386
|
-
function toPosix(p) {
|
|
6387
|
-
return p.replace(/\\/g, "/");
|
|
6388
|
-
}
|
|
6389
|
-
async function statOrNull(p) {
|
|
6390
|
-
try {
|
|
6391
|
-
return await stat3(p);
|
|
6392
|
-
} catch {
|
|
6393
|
-
return null;
|
|
6394
|
-
}
|
|
6395
|
-
}
|
|
6396
|
-
function gitignoreOptions(opts) {
|
|
6397
|
-
const asked = opts.flag === true || opts.flag === void 0 && opts.configured === true;
|
|
6398
|
-
const notice = opts.onNotice;
|
|
6399
|
-
return {
|
|
6400
|
-
respectGitignore: opts.flag ?? opts.configured ?? true,
|
|
6401
|
-
...asked && notice ? {
|
|
6402
|
-
onGitignoreUnavailable: () => {
|
|
6403
|
-
notice(GITIGNORE_UNAVAILABLE);
|
|
6404
|
-
}
|
|
6405
|
-
} : {}
|
|
6406
|
-
};
|
|
6407
|
-
}
|
|
6408
|
-
async function resolveTargetSet(opts) {
|
|
6409
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
6410
|
-
const exts = (opts.exts ?? supportedExtensions()).map(
|
|
6411
|
-
(e) => e.toLowerCase().startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
6412
|
-
);
|
|
6413
|
-
const ignore = [...DEFAULT_IGNORE, ...opts.exclude ?? []];
|
|
6414
|
-
const named = /* @__PURE__ */ new Set();
|
|
6415
|
-
const walked = /* @__PURE__ */ new Set();
|
|
6416
|
-
const keepByExt = (file) => exts.includes(extname(file).toLowerCase());
|
|
6417
|
-
const missing = [];
|
|
6418
|
-
for (const input of opts.inputs) {
|
|
6419
|
-
if (input === STDIN_TOKEN) continue;
|
|
6420
|
-
const posixInput = toPosix(input);
|
|
6421
|
-
const abs = resolve6(cwd, posixInput);
|
|
6422
|
-
const st = await statOrNull(abs);
|
|
6423
|
-
if (st?.isFile()) {
|
|
6424
|
-
named.add(toPosix(relative4(cwd, abs)));
|
|
6425
|
-
continue;
|
|
6426
|
-
}
|
|
6427
|
-
if (st?.isDirectory()) {
|
|
6428
|
-
const found2 = await fg(`${posixInput}/**/*`, {
|
|
6429
|
-
cwd,
|
|
6430
|
-
ignore,
|
|
6431
|
-
onlyFiles: true,
|
|
6432
|
-
dot: false
|
|
6433
|
-
});
|
|
6434
|
-
for (const f of found2) if (keepByExt(f)) walked.add(f);
|
|
6435
|
-
continue;
|
|
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.`);
|
|
6436
6888
|
}
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
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
|
+
);
|
|
6440
6895
|
}
|
|
6441
|
-
|
|
6442
|
-
cwd,
|
|
6443
|
-
ignore,
|
|
6444
|
-
onlyFiles: true,
|
|
6445
|
-
dot: false
|
|
6446
|
-
});
|
|
6447
|
-
for (const f of found) if (keepByExt(f)) walked.add(f);
|
|
6896
|
+
entries[file] = value;
|
|
6448
6897
|
}
|
|
6449
|
-
|
|
6450
|
-
|
|
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;
|
|
6451
6906
|
throw new DocmetaError(
|
|
6452
|
-
|
|
6907
|
+
`Baseline "${source}" could not be read: ${err.message}`
|
|
6453
6908
|
);
|
|
6454
6909
|
}
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
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);
|
|
6462
6948
|
}
|
|
6463
|
-
} else {
|
|
6464
|
-
opts.onGitignoreUnavailable?.();
|
|
6465
6949
|
}
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
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;
|
|
6470
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 };
|
|
6471
6977
|
}
|
|
6472
|
-
function
|
|
6473
|
-
|
|
6474
|
-
const
|
|
6475
|
-
|
|
6476
|
-
if (p.exts && p.exts.length > 0) {
|
|
6477
|
-
notes.push(`extensions: ${p.exts.join(", ")}`);
|
|
6478
|
-
}
|
|
6479
|
-
if (p.exclude && p.exclude.length > 0) {
|
|
6480
|
-
notes.push(`excludes: ${p.exclude.map((e) => `"${e}"`).join(", ")}`);
|
|
6481
|
-
}
|
|
6482
|
-
if (p.gitignoreSkipped && p.gitignoreSkipped > 0) {
|
|
6483
|
-
notes.push(
|
|
6484
|
-
`.gitignore skipped ${p.gitignoreSkipped} (pass --no-gitignore to check them)`
|
|
6485
|
-
);
|
|
6486
|
-
}
|
|
6487
|
-
throw new DocmetaError(
|
|
6488
|
-
[
|
|
6489
|
-
`No files matched. Patterns tried: ${tried}.`,
|
|
6490
|
-
...notes.length > 0 ? [`Filters applied \u2014 ${notes.join("; ")}.`] : [],
|
|
6491
|
-
`Nothing was ${p.action}, so this is an error rather than a pass.`,
|
|
6492
|
-
`Pass --allow-empty (or set allowEmpty: true) if matching nothing is expected.`
|
|
6493
|
-
].join("\n")
|
|
6494
|
-
);
|
|
6978
|
+
function countFingerprints(baseline) {
|
|
6979
|
+
let n = 0;
|
|
6980
|
+
for (const prints of Object.values(baseline.entries)) n += prints.length;
|
|
6981
|
+
return n;
|
|
6495
6982
|
}
|
|
6496
6983
|
|
|
6497
6984
|
// src/meta/core/projection.ts
|
|
@@ -6898,16 +7385,16 @@ function parseErrorResult(file, format, message, keyword) {
|
|
|
6898
7385
|
}
|
|
6899
7386
|
function resolveBaselineRequest(opts, configured, configDir, cwd) {
|
|
6900
7387
|
const named = (label) => ({
|
|
6901
|
-
absPath:
|
|
7388
|
+
absPath: resolve9(cwd, label),
|
|
6902
7389
|
label
|
|
6903
7390
|
});
|
|
6904
7391
|
const implied = () => {
|
|
6905
7392
|
const base = configDir ?? cwd;
|
|
6906
7393
|
if (configured !== void 0) {
|
|
6907
|
-
return { absPath:
|
|
7394
|
+
return { absPath: resolve9(base, configured), label: configured };
|
|
6908
7395
|
}
|
|
6909
|
-
const current =
|
|
6910
|
-
const legacy =
|
|
7396
|
+
const current = resolve9(base, DEFAULT_BASELINE_PATH);
|
|
7397
|
+
const legacy = resolve9(base, LEGACY_BASELINE_PATH);
|
|
6911
7398
|
if (!existsSync2(current) && existsSync2(legacy)) {
|
|
6912
7399
|
warn(
|
|
6913
7400
|
`"${LEGACY_BASELINE_PATH}" is the pre-rename baseline file name. Rename it to "${DEFAULT_BASELINE_PATH}".`
|
|
@@ -6975,6 +7462,11 @@ async function runValidate(opts) {
|
|
|
6975
7462
|
action: "validated"
|
|
6976
7463
|
});
|
|
6977
7464
|
const trustRoot = schemaTrustRoot(cwd, configDir);
|
|
7465
|
+
const sidecars = await loadSidecars(config, {
|
|
7466
|
+
configDir: configDir ?? cwd,
|
|
7467
|
+
base,
|
|
7468
|
+
offline: opts.offline ?? config?.offline ?? false
|
|
7469
|
+
});
|
|
6978
7470
|
const validator = new Validator(
|
|
6979
7471
|
schemaLoadOptions({
|
|
6980
7472
|
// The config's directory when a config governs the run, so one project
|
|
@@ -6990,9 +7482,14 @@ async function runValidate(opts) {
|
|
|
6990
7482
|
})
|
|
6991
7483
|
);
|
|
6992
7484
|
const results = [];
|
|
7485
|
+
const joinHits = /* @__PURE__ */ new Map();
|
|
6993
7486
|
const scoped = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
|
|
6994
7487
|
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;
|
|
6995
7488
|
const configuredChecks = config?.checks ?? [];
|
|
7489
|
+
if (!scoped) {
|
|
7490
|
+
const orphans = orphanEntries(sidecars, files, base);
|
|
7491
|
+
if (orphans.length > 0) throw orphanError(orphans);
|
|
7492
|
+
}
|
|
6996
7493
|
const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
|
|
6997
7494
|
const checkEntries = [];
|
|
6998
7495
|
const checkResolved = /* @__PURE__ */ new Map();
|
|
@@ -7015,6 +7512,16 @@ async function runValidate(opts) {
|
|
|
7015
7512
|
);
|
|
7016
7513
|
return;
|
|
7017
7514
|
}
|
|
7515
|
+
const merged = mergeSidecars(label, extracted, sidecars, base);
|
|
7516
|
+
extracted = merged.extracted;
|
|
7517
|
+
for (const j of merged.joins) {
|
|
7518
|
+
const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
|
|
7519
|
+
const line = extracted.lineFor(j.field);
|
|
7520
|
+
const hits = byValue.get(j.value) ?? [];
|
|
7521
|
+
hits.push({ label, file: j.file, ...line != null ? { line } : {} });
|
|
7522
|
+
byValue.set(j.value, hits);
|
|
7523
|
+
joinHits.set(j.field, byValue);
|
|
7524
|
+
}
|
|
7018
7525
|
if (checksWillRun) checkEntries.push({ label, extracted });
|
|
7019
7526
|
let resolved;
|
|
7020
7527
|
try {
|
|
@@ -7044,7 +7551,8 @@ async function runValidate(opts) {
|
|
|
7044
7551
|
extracted.data,
|
|
7045
7552
|
schemaSet,
|
|
7046
7553
|
extracted.lineFor,
|
|
7047
|
-
extracted.colFor
|
|
7554
|
+
extracted.colFor,
|
|
7555
|
+
merged.locate
|
|
7048
7556
|
);
|
|
7049
7557
|
} catch (err) {
|
|
7050
7558
|
if (!(err instanceof DocmetaError) || resolved.source !== "document") {
|
|
@@ -7055,6 +7563,17 @@ async function runValidate(opts) {
|
|
|
7055
7563
|
);
|
|
7056
7564
|
return;
|
|
7057
7565
|
}
|
|
7566
|
+
for (const { key, file } of merged.collisions) {
|
|
7567
|
+
const line = extracted.lineFor(key);
|
|
7568
|
+
errors.push({
|
|
7569
|
+
schema: SIDECAR_OWNED_SCHEMA,
|
|
7570
|
+
keyword: SIDECAR_KEYWORD,
|
|
7571
|
+
subject: key,
|
|
7572
|
+
instancePath: sidecarPointer(key),
|
|
7573
|
+
message: `"${key}" is owned by sidecar ${file}; remove it from the document`,
|
|
7574
|
+
...line != null ? { line } : {}
|
|
7575
|
+
});
|
|
7576
|
+
}
|
|
7058
7577
|
results.push({
|
|
7059
7578
|
file: label,
|
|
7060
7579
|
format: extractor.name,
|
|
@@ -7073,9 +7592,37 @@ async function runValidate(opts) {
|
|
|
7073
7592
|
await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
|
|
7074
7593
|
}
|
|
7075
7594
|
for (const file of files) {
|
|
7076
|
-
const content = await
|
|
7595
|
+
const content = await readFile6(resolve9(base, file), "utf8");
|
|
7077
7596
|
await processOne(file, content, extname2(file));
|
|
7078
7597
|
}
|
|
7598
|
+
if (sidecars) {
|
|
7599
|
+
const byLabel = new Map(results.map((r) => [r.file, r]));
|
|
7600
|
+
const matched = /* @__PURE__ */ new Map();
|
|
7601
|
+
for (const [field, byValue] of joinHits) {
|
|
7602
|
+
matched.set(field, new Set(byValue.keys()));
|
|
7603
|
+
for (const [value, hits] of byValue) {
|
|
7604
|
+
if (hits.length < 2) continue;
|
|
7605
|
+
for (const hit of hits) {
|
|
7606
|
+
const others = hits.filter((h) => h.label !== hit.label).map((h) => h.label).join(", ");
|
|
7607
|
+
const result = byLabel.get(hit.label);
|
|
7608
|
+
if (!result) continue;
|
|
7609
|
+
result.errors.push({
|
|
7610
|
+
schema: SIDECAR_DUPLICATE_SCHEMA,
|
|
7611
|
+
keyword: SIDECAR_KEYWORD,
|
|
7612
|
+
subject: value,
|
|
7613
|
+
instancePath: sidecarPointer(field),
|
|
7614
|
+
message: `${hits.length} documents carry ${field} "${value}"; ${hit.file} cannot tell them apart (${others})`,
|
|
7615
|
+
...hit.line != null ? { line: hit.line } : {}
|
|
7616
|
+
});
|
|
7617
|
+
result.ok = false;
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
if (!scoped) {
|
|
7622
|
+
const orphans = orphanJoins(sidecars, matched);
|
|
7623
|
+
if (orphans.length > 0) throw orphanError(orphans);
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7079
7626
|
if (configuredChecks.length > 0 && opts.checks !== false) {
|
|
7080
7627
|
if (scoped) {
|
|
7081
7628
|
opts.onNotice?.("corpus checks skipped: run is scoped");
|
|
@@ -7165,14 +7712,14 @@ async function settleBaseline(results, request, ctx) {
|
|
|
7165
7712
|
}
|
|
7166
7713
|
|
|
7167
7714
|
// src/meta/commands/get.ts
|
|
7168
|
-
import { readFile as
|
|
7169
|
-
import { resolve as
|
|
7715
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
7716
|
+
import { resolve as resolve10, extname as extname3 } from "node:path";
|
|
7170
7717
|
async function runGet(opts) {
|
|
7171
7718
|
const cwd = opts.cwd ?? process.cwd();
|
|
7172
7719
|
if (opts.fields.length === 0) {
|
|
7173
7720
|
throw new DocmetaError("Specify at least one field to get.");
|
|
7174
7721
|
}
|
|
7175
|
-
const { config, inputs, base } = await resolveRunConfig({
|
|
7722
|
+
const { config, inputs, base, configDir } = await resolveRunConfig({
|
|
7176
7723
|
cwd,
|
|
7177
7724
|
configPath: opts.configPath,
|
|
7178
7725
|
noConfig: opts.noConfig,
|
|
@@ -7218,6 +7765,11 @@ async function runGet(opts) {
|
|
|
7218
7765
|
action: "read"
|
|
7219
7766
|
});
|
|
7220
7767
|
const out = [];
|
|
7768
|
+
const sidecars = await loadSidecars(config, {
|
|
7769
|
+
configDir: configDir ?? cwd,
|
|
7770
|
+
base,
|
|
7771
|
+
offline: opts.offline ?? config?.offline ?? false
|
|
7772
|
+
});
|
|
7221
7773
|
const readOne = (label, content, extension) => {
|
|
7222
7774
|
const extractor = forced ?? extractorForExtension(extension);
|
|
7223
7775
|
if (!extractor) {
|
|
@@ -7227,9 +7779,14 @@ async function runGet(opts) {
|
|
|
7227
7779
|
}
|
|
7228
7780
|
let extracted;
|
|
7229
7781
|
try {
|
|
7230
|
-
extracted =
|
|
7231
|
-
|
|
7232
|
-
|
|
7782
|
+
extracted = mergeSidecars(
|
|
7783
|
+
label,
|
|
7784
|
+
extractor.extract(content, label, {
|
|
7785
|
+
elements: resolveElements(label, config)
|
|
7786
|
+
}),
|
|
7787
|
+
sidecars,
|
|
7788
|
+
base
|
|
7789
|
+
).extracted;
|
|
7233
7790
|
} catch (err) {
|
|
7234
7791
|
if (err instanceof DocmetaError) throw err;
|
|
7235
7792
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -7249,7 +7806,7 @@ async function runGet(opts) {
|
|
|
7249
7806
|
readOne("<stdin>", opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
7250
7807
|
}
|
|
7251
7808
|
for (const file of files) {
|
|
7252
|
-
const content = await
|
|
7809
|
+
const content = await readFile7(resolve10(base, file), "utf8");
|
|
7253
7810
|
readOne(file, content, extname3(file));
|
|
7254
7811
|
}
|
|
7255
7812
|
return out;
|
|
@@ -7286,16 +7843,16 @@ function parseJsonPointer(pointer) {
|
|
|
7286
7843
|
|
|
7287
7844
|
// src/meta/commands/schemas.ts
|
|
7288
7845
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7289
|
-
import { mkdir as mkdir2, readFile as
|
|
7290
|
-
import { dirname as
|
|
7291
|
-
import { parseDocument as
|
|
7846
|
+
import { mkdir as mkdir2, readFile as readFile8 } from "node:fs/promises";
|
|
7847
|
+
import { dirname as dirname5, extname as extname4, isAbsolute as isAbsolute3, join as join5, relative as relative7, resolve as resolve11, sep as sep3 } from "node:path";
|
|
7848
|
+
import { parseDocument as parseDocument4, parse as parseYaml3, stringify } from "yaml";
|
|
7292
7849
|
function getSchemasInfo() {
|
|
7293
7850
|
return { builtins: listBuiltins(), formats: listFormats() };
|
|
7294
7851
|
}
|
|
7295
7852
|
var DEFAULT_VENDOR_DIR = "./schema";
|
|
7296
7853
|
var DEFAULT_CONFIG_NAME = "manni.config.yaml";
|
|
7297
7854
|
function posixRelative(from, to) {
|
|
7298
|
-
return
|
|
7855
|
+
return relative7(from, to).split(sep3).join("/");
|
|
7299
7856
|
}
|
|
7300
7857
|
function vendorFileName(url) {
|
|
7301
7858
|
let parsed2;
|
|
@@ -7360,7 +7917,7 @@ function renderConfig(existing, entries, section) {
|
|
|
7360
7917
|
if (existing === null) {
|
|
7361
7918
|
return stringify({ [META_SECTION]: { schemas: entries } });
|
|
7362
7919
|
}
|
|
7363
|
-
const doc =
|
|
7920
|
+
const doc = parseDocument4(existing);
|
|
7364
7921
|
doc.setIn(section === void 0 ? ["schemas"] : [section, "schemas"], entries);
|
|
7365
7922
|
return doc.toString();
|
|
7366
7923
|
}
|
|
@@ -7381,13 +7938,13 @@ async function runVendorSchema(opts) {
|
|
|
7381
7938
|
);
|
|
7382
7939
|
}
|
|
7383
7940
|
const loaded = await loadConfig(opts.configPath, cwd);
|
|
7384
|
-
const configPath = loaded?.path ??
|
|
7941
|
+
const configPath = loaded?.path ?? resolve11(cwd, DEFAULT_CONFIG_NAME);
|
|
7385
7942
|
const configDir = loaded?.dir ?? cwd;
|
|
7386
|
-
const absDir =
|
|
7943
|
+
const absDir = resolve11(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
|
|
7387
7944
|
const absFile = join5(absDir, vendorFileName(url));
|
|
7388
7945
|
await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
|
|
7389
7946
|
const fromConfig = posixRelative(configDir, absFile);
|
|
7390
|
-
const ref =
|
|
7947
|
+
const ref = isAbsolute3(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
|
|
7391
7948
|
const { bytes } = await fetchSchemaBytes(url, {
|
|
7392
7949
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
7393
7950
|
...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
|
|
@@ -7402,7 +7959,7 @@ async function runVendorSchema(opts) {
|
|
|
7402
7959
|
}
|
|
7403
7960
|
let unchanged = false;
|
|
7404
7961
|
if (existsSync3(absFile)) {
|
|
7405
|
-
const current = await
|
|
7962
|
+
const current = await readFile8(absFile);
|
|
7406
7963
|
unchanged = bytes.equals(current);
|
|
7407
7964
|
if (!unchanged && !folded.replaced) {
|
|
7408
7965
|
throw new DocmetaError(
|
|
@@ -7412,7 +7969,7 @@ async function runVendorSchema(opts) {
|
|
|
7412
7969
|
}
|
|
7413
7970
|
await mkdir2(absDir, { recursive: true });
|
|
7414
7971
|
await writeFileAtomic(absFile, bytes);
|
|
7415
|
-
const existing = loaded ? await
|
|
7972
|
+
const existing = loaded ? await readFile8(configPath, "utf8") : null;
|
|
7416
7973
|
const section = loaded ? loaded.section : META_SECTION;
|
|
7417
7974
|
const text = renderConfig(existing, folded.entries, section);
|
|
7418
7975
|
reparseConfig(text, posixRelative(cwd, configPath) || DEFAULT_CONFIG_NAME, section);
|
|
@@ -7531,7 +8088,7 @@ async function runInferSchema(opts) {
|
|
|
7531
8088
|
`--min-coverage must be a percentage between 0 and 100, got ${minCoverage}.`
|
|
7532
8089
|
);
|
|
7533
8090
|
}
|
|
7534
|
-
const { config, inputs, base } = await resolveRunConfig({
|
|
8091
|
+
const { config, inputs, base, configDir } = await resolveRunConfig({
|
|
7535
8092
|
cwd,
|
|
7536
8093
|
configPath: opts.configPath,
|
|
7537
8094
|
noConfig: opts.noConfig,
|
|
@@ -7539,6 +8096,11 @@ async function runInferSchema(opts) {
|
|
|
7539
8096
|
onConfigLoaded: opts.onConfigLoaded
|
|
7540
8097
|
});
|
|
7541
8098
|
const usingStdin = inputs.includes(STDIN_TOKEN);
|
|
8099
|
+
const sidecars = await loadSidecars(config, {
|
|
8100
|
+
configDir: configDir ?? cwd,
|
|
8101
|
+
base,
|
|
8102
|
+
offline: opts.offline ?? config?.offline ?? false
|
|
8103
|
+
});
|
|
7542
8104
|
if (inputs.length === 0) {
|
|
7543
8105
|
throw new DocmetaError(
|
|
7544
8106
|
"No files to scan. Pass paths/globs, or add `paths:` under `meta:` in manni.config.yaml."
|
|
@@ -7557,10 +8119,10 @@ async function runInferSchema(opts) {
|
|
|
7557
8119
|
}
|
|
7558
8120
|
let absOut;
|
|
7559
8121
|
if (opts.out !== void 0) {
|
|
7560
|
-
absOut =
|
|
8122
|
+
absOut = resolve11(cwd, opts.out);
|
|
7561
8123
|
await assertNotIgnored(
|
|
7562
8124
|
absOut,
|
|
7563
|
-
|
|
8125
|
+
dirname5(absOut),
|
|
7564
8126
|
cwd,
|
|
7565
8127
|
opts.onNotice,
|
|
7566
8128
|
DRAFT_IGNORE_TEXT
|
|
@@ -7611,9 +8173,14 @@ async function runInferSchema(opts) {
|
|
|
7611
8173
|
filesScanned += 1;
|
|
7612
8174
|
let extracted;
|
|
7613
8175
|
try {
|
|
7614
|
-
extracted =
|
|
7615
|
-
|
|
7616
|
-
|
|
8176
|
+
extracted = mergeSidecars(
|
|
8177
|
+
label,
|
|
8178
|
+
extractor.extract(content, label, {
|
|
8179
|
+
elements: resolveElements(label, config)
|
|
8180
|
+
}),
|
|
8181
|
+
sidecars,
|
|
8182
|
+
base
|
|
8183
|
+
).extracted;
|
|
7617
8184
|
} catch (err) {
|
|
7618
8185
|
unreadable.push({
|
|
7619
8186
|
file: label,
|
|
@@ -7635,7 +8202,7 @@ async function runInferSchema(opts) {
|
|
|
7635
8202
|
scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
|
|
7636
8203
|
}
|
|
7637
8204
|
for (const file of files) {
|
|
7638
|
-
const content = await
|
|
8205
|
+
const content = await readFile8(resolve11(base, file), "utf8");
|
|
7639
8206
|
scanOne(file, content, extname4(file));
|
|
7640
8207
|
}
|
|
7641
8208
|
for (const u of unreadable) {
|
|
@@ -7677,7 +8244,7 @@ async function runInferSchema(opts) {
|
|
|
7677
8244
|
};
|
|
7678
8245
|
const keys = kept.map(({ sawEmptyString: _drop, ...rest }) => rest);
|
|
7679
8246
|
if (absOut !== void 0) {
|
|
7680
|
-
await mkdir2(
|
|
8247
|
+
await mkdir2(dirname5(absOut), { recursive: true });
|
|
7681
8248
|
await writeFileAtomic(absOut, `${JSON.stringify(draft, null, 2)}
|
|
7682
8249
|
`);
|
|
7683
8250
|
}
|
|
@@ -7696,9 +8263,9 @@ async function runInferSchema(opts) {
|
|
|
7696
8263
|
// src/meta/commands/query.ts
|
|
7697
8264
|
import { randomBytes } from "node:crypto";
|
|
7698
8265
|
import { existsSync as existsSync4 } from "node:fs";
|
|
7699
|
-
import { mkdir as mkdir3, open, readFile as
|
|
7700
|
-
import { dirname as
|
|
7701
|
-
import { parseDocument as
|
|
8266
|
+
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 isAbsolute4, resolve as resolve12, extname as extname5, sep as sep4 } from "node:path";
|
|
8268
|
+
import { parseDocument as parseDocument5, isMap as isMap4, isSeq as isSeq2, isScalar as isScalar3 } from "yaml";
|
|
7702
8269
|
async function runQuery(opts) {
|
|
7703
8270
|
const cwd = opts.cwd ?? process.cwd();
|
|
7704
8271
|
const sql = opts.sql.trim();
|
|
@@ -7778,6 +8345,11 @@ async function runQuery(opts) {
|
|
|
7778
8345
|
action: "queried"
|
|
7779
8346
|
});
|
|
7780
8347
|
const entries = [];
|
|
8348
|
+
const sidecars = await loadSidecars(config, {
|
|
8349
|
+
configDir: configDir ?? cwd,
|
|
8350
|
+
base,
|
|
8351
|
+
offline: opts.offline ?? config?.offline ?? false
|
|
8352
|
+
});
|
|
7781
8353
|
const readOne = (label, content, extension) => {
|
|
7782
8354
|
const extractor = forced ?? extractorForExtension(extension);
|
|
7783
8355
|
if (!extractor) {
|
|
@@ -7785,9 +8357,14 @@ async function runQuery(opts) {
|
|
|
7785
8357
|
`Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
7786
8358
|
);
|
|
7787
8359
|
}
|
|
7788
|
-
const extracted =
|
|
7789
|
-
|
|
7790
|
-
|
|
8360
|
+
const extracted = mergeSidecars(
|
|
8361
|
+
label,
|
|
8362
|
+
extractor.extract(content, label, {
|
|
8363
|
+
elements: resolveElements(label, config)
|
|
8364
|
+
}),
|
|
8365
|
+
sidecars,
|
|
8366
|
+
base
|
|
8367
|
+
).extracted;
|
|
7791
8368
|
entries.push({ label, extracted, extractor });
|
|
7792
8369
|
};
|
|
7793
8370
|
if (usingStdin) {
|
|
@@ -7805,10 +8382,10 @@ async function runQuery(opts) {
|
|
|
7805
8382
|
readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
|
|
7806
8383
|
}
|
|
7807
8384
|
for (const file of files) {
|
|
7808
|
-
const content = await
|
|
8385
|
+
const content = await readFile9(resolve12(base, file), "utf8");
|
|
7809
8386
|
readOne(file, content, extname5(file));
|
|
7810
8387
|
}
|
|
7811
|
-
const db = opts.db === void 0 ? void 0 : { resolved:
|
|
8388
|
+
const db = opts.db === void 0 ? void 0 : { resolved: resolve12(cwd, opts.db), display: opts.db };
|
|
7812
8389
|
const run = await runSql(sql, entries, {
|
|
7813
8390
|
target: db,
|
|
7814
8391
|
params: boundParams,
|
|
@@ -7820,6 +8397,7 @@ async function runQuery(opts) {
|
|
|
7820
8397
|
configDir,
|
|
7821
8398
|
configPath,
|
|
7822
8399
|
trustRoot: schemaTrustRoot(cwd, configDir),
|
|
8400
|
+
sidecars,
|
|
7823
8401
|
onNotice: opts.onNotice
|
|
7824
8402
|
});
|
|
7825
8403
|
return { ...run, frame: { cwd, base: configDir ?? cwd, runBase: base } };
|
|
@@ -7883,7 +8461,7 @@ async function runSql(sql, entries, ctx) {
|
|
|
7883
8461
|
);
|
|
7884
8462
|
const { DatabaseSync } = await loadSqlite();
|
|
7885
8463
|
if (target) {
|
|
7886
|
-
await mkdir3(
|
|
8464
|
+
await mkdir3(dirname6(target.resolved), { recursive: true });
|
|
7887
8465
|
await prepareDbTarget(target.resolved, target.display);
|
|
7888
8466
|
}
|
|
7889
8467
|
let db;
|
|
@@ -8335,20 +8913,20 @@ function displayPath(ctx, abs) {
|
|
|
8335
8913
|
}
|
|
8336
8914
|
function assertSchemaWriteWithin(ctx, abs) {
|
|
8337
8915
|
const within = posixRelative(ctx.trustRoot.dir, abs);
|
|
8338
|
-
if (within !== "" && !within.startsWith("..") && !
|
|
8916
|
+
if (within !== "" && !within.startsWith("..") && !isAbsolute4(within)) return;
|
|
8339
8917
|
throw new DocmetaError(
|
|
8340
8918
|
`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.`
|
|
8341
8919
|
);
|
|
8342
8920
|
}
|
|
8343
8921
|
function foldPath(p) {
|
|
8344
|
-
const abs =
|
|
8922
|
+
const abs = resolve12(p);
|
|
8345
8923
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
8346
8924
|
}
|
|
8347
8925
|
function filePinsByPath(ctx) {
|
|
8348
8926
|
const byPath = /* @__PURE__ */ new Map();
|
|
8349
8927
|
for (const [ref, pin] of collectSchemaPins(ctx.config)) {
|
|
8350
8928
|
if (classifyRef(ref).kind !== "file") continue;
|
|
8351
|
-
byPath.set(foldPath(
|
|
8929
|
+
byPath.set(foldPath(resolve12(ctx.cwd, ref)), pin);
|
|
8352
8930
|
}
|
|
8353
8931
|
return byPath;
|
|
8354
8932
|
}
|
|
@@ -8376,13 +8954,13 @@ async function loadSetMembers(refs, ctx) {
|
|
|
8376
8954
|
});
|
|
8377
8955
|
continue;
|
|
8378
8956
|
}
|
|
8379
|
-
const abs =
|
|
8957
|
+
const abs = resolve12(ctx.cwd, ref);
|
|
8380
8958
|
const canon = foldPath(abs);
|
|
8381
8959
|
if (seenFiles.has(canon)) continue;
|
|
8382
8960
|
seenFiles.add(canon);
|
|
8383
8961
|
let text;
|
|
8384
8962
|
try {
|
|
8385
|
-
text = await
|
|
8963
|
+
text = await readFile9(abs, "utf8");
|
|
8386
8964
|
} catch {
|
|
8387
8965
|
throw new DocmetaError(
|
|
8388
8966
|
`Schema file not found: "${ref}" (looked at ${abs}).`
|
|
@@ -8576,7 +9154,7 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
8576
9154
|
const [, name2, ver] = target.builtinId.split(":");
|
|
8577
9155
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
8578
9156
|
const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
|
|
8579
|
-
schemaAbs =
|
|
9157
|
+
schemaAbs = resolve12(ctx.configDir ?? ctx.cwd, "schemas", forkName);
|
|
8580
9158
|
assertSchemaWriteWithin(ctx, schemaAbs);
|
|
8581
9159
|
if (existsSync4(schemaAbs)) {
|
|
8582
9160
|
throw new DocmetaError(
|
|
@@ -8585,7 +9163,7 @@ async function planSchemaMutation(ops, entries, ctx) {
|
|
|
8585
9163
|
}
|
|
8586
9164
|
await assertNotIgnored(
|
|
8587
9165
|
schemaAbs,
|
|
8588
|
-
|
|
9166
|
+
dirname6(schemaAbs),
|
|
8589
9167
|
ctx.cwd,
|
|
8590
9168
|
ctx.onNotice,
|
|
8591
9169
|
FORK_IGNORE_TEXT
|
|
@@ -8732,18 +9310,18 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
8732
9310
|
if (configPath === void 0 || configDir === void 0) {
|
|
8733
9311
|
return { changes: [], repointed: false };
|
|
8734
9312
|
}
|
|
8735
|
-
const text = await
|
|
8736
|
-
const doc =
|
|
9313
|
+
const text = await readFile9(configPath, "utf8");
|
|
9314
|
+
const doc = parseDocument5(stripBom(text));
|
|
8737
9315
|
const display = displayPath(ctx, configPath);
|
|
8738
9316
|
const rawRefOf = (item) => {
|
|
8739
|
-
if (
|
|
8740
|
-
if (
|
|
9317
|
+
if (isScalar3(item) && typeof item.value === "string") return item.value;
|
|
9318
|
+
if (isMap4(item)) {
|
|
8741
9319
|
const ref = item.get("ref");
|
|
8742
9320
|
if (typeof ref === "string") return ref;
|
|
8743
9321
|
}
|
|
8744
9322
|
return void 0;
|
|
8745
9323
|
};
|
|
8746
|
-
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !
|
|
9324
|
+
const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute4(raw) ? resolve12(configDir, raw) : raw;
|
|
8747
9325
|
const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
|
|
8748
9326
|
const seqResolvesToRunSet = (node) => {
|
|
8749
9327
|
if (!isSeq2(node)) return false;
|
|
@@ -8759,10 +9337,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
8759
9337
|
for (const item of node.items) {
|
|
8760
9338
|
const raw = rawRefOf(item);
|
|
8761
9339
|
if (raw === void 0 || !wants(raw)) continue;
|
|
8762
|
-
if (
|
|
9340
|
+
if (isScalar3(item)) {
|
|
8763
9341
|
item.value = spec.newRef;
|
|
8764
9342
|
n += 1;
|
|
8765
|
-
} else if (
|
|
9343
|
+
} else if (isMap4(item)) {
|
|
8766
9344
|
item.set("ref", spec.newRef);
|
|
8767
9345
|
n += 1;
|
|
8768
9346
|
}
|
|
@@ -8774,7 +9352,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
8774
9352
|
const overrides = doc.get("overrides", true);
|
|
8775
9353
|
if (isSeq2(overrides)) {
|
|
8776
9354
|
for (const entry of overrides.items) {
|
|
8777
|
-
if (
|
|
9355
|
+
if (isMap4(entry)) repointCount += repoint(entry.get("schemas", true));
|
|
8778
9356
|
}
|
|
8779
9357
|
}
|
|
8780
9358
|
const repointed = repointCount > 0;
|
|
@@ -8783,10 +9361,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
|
|
|
8783
9361
|
if (edit.integrity && isSeq2(topSeq)) {
|
|
8784
9362
|
const wantPath = foldPath(edit.integrity.path);
|
|
8785
9363
|
for (const item of topSeq.items) {
|
|
8786
|
-
if (!
|
|
9364
|
+
if (!isMap4(item)) continue;
|
|
8787
9365
|
const raw = rawRefOf(item);
|
|
8788
9366
|
if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
|
|
8789
|
-
const abs =
|
|
9367
|
+
const abs = isAbsolute4(raw) ? raw : resolve12(configDir, raw);
|
|
8790
9368
|
if (foldPath(abs) !== wantPath) continue;
|
|
8791
9369
|
pinFrom = item.get("integrity");
|
|
8792
9370
|
item.set("integrity", edit.integrity.value);
|
|
@@ -9007,7 +9585,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9007
9585
|
`"${file}": no writable format for that extension.`
|
|
9008
9586
|
);
|
|
9009
9587
|
}
|
|
9010
|
-
if (existsSync4(
|
|
9588
|
+
if (existsSync4(resolve12(ctx.base, file))) {
|
|
9011
9589
|
throw new DocmetaError(
|
|
9012
9590
|
`"${file}" already exists; INSERT creates new files only.`
|
|
9013
9591
|
);
|
|
@@ -9026,26 +9604,81 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
|
|
|
9026
9604
|
`"${from}" -> "${to}": a rename may not change the extension.`
|
|
9027
9605
|
);
|
|
9028
9606
|
}
|
|
9029
|
-
if (existsSync4(
|
|
9607
|
+
if (existsSync4(resolve12(ctx.base, to))) {
|
|
9030
9608
|
throw new DocmetaError(
|
|
9031
9609
|
`"${to}" already exists; the rename would overwrite it.`
|
|
9032
9610
|
);
|
|
9033
9611
|
}
|
|
9034
9612
|
changes.push({ file: from, renamed: to, written: false });
|
|
9035
9613
|
}
|
|
9614
|
+
refuseSidecarWrites(changes, entries, ctx);
|
|
9036
9615
|
return changes;
|
|
9037
9616
|
}
|
|
9038
9617
|
function validateNewPath(p, base) {
|
|
9039
9618
|
const refuse = (why) => {
|
|
9040
9619
|
throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
|
|
9041
9620
|
};
|
|
9042
|
-
if (
|
|
9621
|
+
if (isAbsolute4(p)) refuse("it is absolute");
|
|
9043
9622
|
if (p.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
9044
9623
|
refuse("it traverses upward");
|
|
9045
9624
|
}
|
|
9046
|
-
const abs =
|
|
9047
|
-
if (abs ===
|
|
9048
|
-
if (!abs.startsWith(
|
|
9625
|
+
const abs = resolve12(base, p);
|
|
9626
|
+
if (abs === resolve12(base)) refuse("it points at the corpus root, not a file");
|
|
9627
|
+
if (!abs.startsWith(resolve12(base) + sep4)) refuse("it escapes the corpus");
|
|
9628
|
+
}
|
|
9629
|
+
function refuseSidecarWrites(changes, entries, ctx) {
|
|
9630
|
+
const index = ctx.sidecars;
|
|
9631
|
+
if (!index) return;
|
|
9632
|
+
const owned = (key) => index.owners.get(key);
|
|
9633
|
+
const refuse = (file, key, owner) => {
|
|
9634
|
+
throw new DocmetaError(
|
|
9635
|
+
`"${file}": "${key}" is owned by sidecar ${owner}; edit the sidecar file instead.`
|
|
9636
|
+
);
|
|
9637
|
+
};
|
|
9638
|
+
const data = new Map(entries.map((e) => [e.label, e.extracted.data]));
|
|
9639
|
+
for (const c of changes) {
|
|
9640
|
+
if ("schema" in c || "config" in c) continue;
|
|
9641
|
+
if ("cleared" in c) {
|
|
9642
|
+
for (const key of Object.keys(data.get(c.file) ?? {})) {
|
|
9643
|
+
const owner = owned(key);
|
|
9644
|
+
if (owner !== void 0) refuse(c.file, key, owner);
|
|
9645
|
+
}
|
|
9646
|
+
continue;
|
|
9647
|
+
}
|
|
9648
|
+
if ("created" in c) {
|
|
9649
|
+
for (const key of Object.keys(c.to)) {
|
|
9650
|
+
const owner = owned(key);
|
|
9651
|
+
if (owner !== void 0) refuse(c.file, key, owner);
|
|
9652
|
+
}
|
|
9653
|
+
continue;
|
|
9654
|
+
}
|
|
9655
|
+
if ("renamed" in c) {
|
|
9656
|
+
const entry = index.byPath.get(resolve12(ctx.base, c.file));
|
|
9657
|
+
if (entry !== void 0 && entry.size > 0) {
|
|
9658
|
+
const manifest = [...entry.values()][0]?.file ?? "manifest";
|
|
9659
|
+
throw new DocmetaError(
|
|
9660
|
+
`"${c.file}": sidecar ${manifest} names it; rename the manifest entry first.`
|
|
9661
|
+
);
|
|
9662
|
+
}
|
|
9663
|
+
continue;
|
|
9664
|
+
}
|
|
9665
|
+
const keys = "renamedFrom" in c ? [c.key, c.renamedFrom] : [c.key];
|
|
9666
|
+
for (const key of keys) {
|
|
9667
|
+
const owner = owned(key);
|
|
9668
|
+
if (owner !== void 0) refuse(c.file, key, owner);
|
|
9669
|
+
const byValue = index.byField.get(key);
|
|
9670
|
+
if (byValue === void 0) continue;
|
|
9671
|
+
const raw = data.get(c.file)?.[key];
|
|
9672
|
+
const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "boolean" ? String(raw) : void 0;
|
|
9673
|
+
const entry = value === void 0 ? void 0 : byValue.get(value);
|
|
9674
|
+
if (entry !== void 0) {
|
|
9675
|
+
const manifest = entry.values().next().value?.file ?? "manifest";
|
|
9676
|
+
throw new DocmetaError(
|
|
9677
|
+
`"${c.file}": "${key}" is the field sidecar ${manifest} joins on, and this document has an entry; change the manifest first.`
|
|
9678
|
+
);
|
|
9679
|
+
}
|
|
9680
|
+
}
|
|
9681
|
+
}
|
|
9049
9682
|
}
|
|
9050
9683
|
async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
9051
9684
|
const schemaWrites = schemaPlan?.writes ?? [];
|
|
@@ -9073,14 +9706,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9073
9706
|
const pendingWrites = [];
|
|
9074
9707
|
const pendingRenames = [];
|
|
9075
9708
|
for (const [label, ops] of grouped) {
|
|
9076
|
-
const path =
|
|
9709
|
+
const path = resolve12(ctx.base, label);
|
|
9077
9710
|
if (ops.renamedTo !== void 0) {
|
|
9078
9711
|
if (!existsSync4(path)) {
|
|
9079
9712
|
throw new DocmetaError(
|
|
9080
9713
|
`"${label}" is gone from disk since it was read; re-run the query.`
|
|
9081
9714
|
);
|
|
9082
9715
|
}
|
|
9083
|
-
const toDisk =
|
|
9716
|
+
const toDisk = resolve12(ctx.base, ops.renamedTo);
|
|
9084
9717
|
if (existsSync4(toDisk)) {
|
|
9085
9718
|
throw new DocmetaError(
|
|
9086
9719
|
`"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
|
|
@@ -9113,10 +9746,15 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9113
9746
|
}
|
|
9114
9747
|
const entry = byLabel.get(label);
|
|
9115
9748
|
if (!entry) throw new DocmetaError(`No loaded entry for "${label}".`);
|
|
9116
|
-
const content = await
|
|
9117
|
-
const current =
|
|
9118
|
-
|
|
9119
|
-
|
|
9749
|
+
const content = await readFile9(path, "utf8");
|
|
9750
|
+
const current = mergeSidecars(
|
|
9751
|
+
label,
|
|
9752
|
+
entry.extractor.extract(content, label, {
|
|
9753
|
+
elements: resolveElements(label, ctx.config)
|
|
9754
|
+
}),
|
|
9755
|
+
ctx.sidecars,
|
|
9756
|
+
ctx.base
|
|
9757
|
+
).extracted;
|
|
9120
9758
|
if (ops.cleared) {
|
|
9121
9759
|
if (!deepEqual(current.data, entry.extracted.data)) {
|
|
9122
9760
|
throw new DocmetaError(
|
|
@@ -9150,9 +9788,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9150
9788
|
deletions: ops.deletions
|
|
9151
9789
|
});
|
|
9152
9790
|
if (ops.deletions.length > 0) {
|
|
9153
|
-
const check =
|
|
9154
|
-
|
|
9155
|
-
|
|
9791
|
+
const check = mergeSidecars(
|
|
9792
|
+
label,
|
|
9793
|
+
entry.extractor.extract(applied, label, {
|
|
9794
|
+
elements: resolveElements(label, ctx.config)
|
|
9795
|
+
}),
|
|
9796
|
+
ctx.sidecars,
|
|
9797
|
+
ctx.base
|
|
9798
|
+
).extracted;
|
|
9156
9799
|
for (const key of ops.deletions) {
|
|
9157
9800
|
if (check.data[key] !== void 0) {
|
|
9158
9801
|
throw new DocmetaError(
|
|
@@ -9175,7 +9818,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9175
9818
|
}
|
|
9176
9819
|
let now;
|
|
9177
9820
|
try {
|
|
9178
|
-
now = await
|
|
9821
|
+
now = await readFile9(w.path, "utf8");
|
|
9179
9822
|
} catch {
|
|
9180
9823
|
now = void 0;
|
|
9181
9824
|
}
|
|
@@ -9186,10 +9829,10 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
|
|
|
9186
9829
|
}
|
|
9187
9830
|
}
|
|
9188
9831
|
const dirs = /* @__PURE__ */ new Set();
|
|
9189
|
-
for (const w of schemaWrites) dirs.add(
|
|
9190
|
-
for (const r of pendingRenames) dirs.add(
|
|
9832
|
+
for (const w of schemaWrites) dirs.add(dirname6(w.path));
|
|
9833
|
+
for (const r of pendingRenames) dirs.add(dirname6(r.to));
|
|
9191
9834
|
for (const p of pendingWrites) {
|
|
9192
|
-
if (p.ensureDir) dirs.add(
|
|
9835
|
+
if (p.ensureDir) dirs.add(dirname6(p.path));
|
|
9193
9836
|
}
|
|
9194
9837
|
for (const d of dirs) await mkdir3(d, { recursive: true });
|
|
9195
9838
|
const writeSchema = async () => {
|
|
@@ -9304,8 +9947,8 @@ function skipExpression(sql, from) {
|
|
|
9304
9947
|
}
|
|
9305
9948
|
|
|
9306
9949
|
// src/meta/commands/fill.ts
|
|
9307
|
-
import { readFile as
|
|
9308
|
-
import { resolve as
|
|
9950
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
9951
|
+
import { resolve as resolve13, extname as extname6, join as join6 } from "node:path";
|
|
9309
9952
|
import {
|
|
9310
9953
|
completeValidatedJSON,
|
|
9311
9954
|
makeProvider,
|
|
@@ -9578,6 +10221,11 @@ async function runFill(opts) {
|
|
|
9578
10221
|
let turnsUsed = 0;
|
|
9579
10222
|
let inFlight = 0;
|
|
9580
10223
|
const turnsExhausted = () => maxTurns != null && turnsUsed + inFlight >= maxTurns;
|
|
10224
|
+
const sidecars = await loadSidecars(config, {
|
|
10225
|
+
configDir: configDir ?? cwd,
|
|
10226
|
+
base,
|
|
10227
|
+
offline: opts.offline ?? config?.offline ?? false
|
|
10228
|
+
});
|
|
9581
10229
|
const processOne = async (label, content, extension) => {
|
|
9582
10230
|
const extractor = forcedExtractor ?? extractorForExtension(extension);
|
|
9583
10231
|
if (!extractor) {
|
|
@@ -9596,8 +10244,15 @@ async function runFill(opts) {
|
|
|
9596
10244
|
}
|
|
9597
10245
|
const elements = resolveElements(label, config);
|
|
9598
10246
|
let extracted;
|
|
10247
|
+
let merged;
|
|
9599
10248
|
try {
|
|
9600
|
-
|
|
10249
|
+
merged = mergeSidecars(
|
|
10250
|
+
label,
|
|
10251
|
+
extractor.extract(content, label, { elements }),
|
|
10252
|
+
sidecars,
|
|
10253
|
+
base
|
|
10254
|
+
);
|
|
10255
|
+
extracted = merged.extracted;
|
|
9601
10256
|
} catch (err) {
|
|
9602
10257
|
return errorResult(label, extractor.name, err.message);
|
|
9603
10258
|
}
|
|
@@ -9629,7 +10284,8 @@ async function runFill(opts) {
|
|
|
9629
10284
|
extracted.data,
|
|
9630
10285
|
schemaSet,
|
|
9631
10286
|
extracted.lineFor,
|
|
9632
|
-
extracted.colFor
|
|
10287
|
+
extracted.colFor,
|
|
10288
|
+
merged.locate
|
|
9633
10289
|
);
|
|
9634
10290
|
} catch (err) {
|
|
9635
10291
|
if (!(err instanceof DocmetaError) || resolved.source !== "document") {
|
|
@@ -9643,6 +10299,19 @@ async function runFill(opts) {
|
|
|
9643
10299
|
existingErrors,
|
|
9644
10300
|
only
|
|
9645
10301
|
);
|
|
10302
|
+
const ownedCandidates = sidecars ? candidates.flatMap((c) => {
|
|
10303
|
+
const file = sidecars.owners.get(c.key);
|
|
10304
|
+
return file === void 0 ? [] : [{ key: c.key, file }];
|
|
10305
|
+
}) : [];
|
|
10306
|
+
const firstOwned = ownedCandidates[0];
|
|
10307
|
+
if (firstOwned) {
|
|
10308
|
+
return errorResult(
|
|
10309
|
+
label,
|
|
10310
|
+
extractor.name,
|
|
10311
|
+
`"${firstOwned.key}" is owned by sidecar ${firstOwned.file}; manni meta fill cannot write a sidecar key. Add it to the manifest instead.`,
|
|
10312
|
+
schemaSet
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
9646
10315
|
if (candidates.length === 0) {
|
|
9647
10316
|
return {
|
|
9648
10317
|
file: label,
|
|
@@ -9783,9 +10452,9 @@ async function runFill(opts) {
|
|
|
9783
10452
|
const fields = gate(candidates, proposals, threshold);
|
|
9784
10453
|
const accepted = fields.filter((f) => f.written);
|
|
9785
10454
|
if (accepted.length > 0) {
|
|
9786
|
-
const
|
|
10455
|
+
const merged2 = { ...extracted.data, ...patchOf(accepted) };
|
|
9787
10456
|
const after = await validator.validate(
|
|
9788
|
-
|
|
10457
|
+
merged2,
|
|
9789
10458
|
schemaSet,
|
|
9790
10459
|
extracted.lineFor,
|
|
9791
10460
|
extracted.colFor
|
|
@@ -9829,7 +10498,7 @@ async function runFill(opts) {
|
|
|
9829
10498
|
}
|
|
9830
10499
|
const changed = next !== content;
|
|
9831
10500
|
if (changed && !dryRun && label !== "<stdin>") {
|
|
9832
|
-
await writeFileAtomic(
|
|
10501
|
+
await writeFileAtomic(resolve13(base, label), next);
|
|
9833
10502
|
}
|
|
9834
10503
|
return {
|
|
9835
10504
|
file: label,
|
|
@@ -9851,7 +10520,7 @@ async function runFill(opts) {
|
|
|
9851
10520
|
);
|
|
9852
10521
|
}
|
|
9853
10522
|
const fileResults = await mapConcurrent(files, concurrency, async (file) => {
|
|
9854
|
-
const content = await
|
|
10523
|
+
const content = await readFile10(resolve13(base, file), "utf8");
|
|
9855
10524
|
return processOne(file, content, extname6(file));
|
|
9856
10525
|
});
|
|
9857
10526
|
results.push(...fileResults);
|
|
@@ -10169,15 +10838,19 @@ async function mapConcurrent(items, limit, fn) {
|
|
|
10169
10838
|
}
|
|
10170
10839
|
|
|
10171
10840
|
// src/meta/reporters/sarif.ts
|
|
10172
|
-
import { isAbsolute as
|
|
10841
|
+
import { isAbsolute as isAbsolute5 } from "node:path";
|
|
10173
10842
|
|
|
10174
10843
|
// src/meta/reporters/rule-id.ts
|
|
10175
10844
|
var SYNTHETIC_SCHEMA = "(parse)";
|
|
10176
10845
|
var PARSE_ERROR_RULE = "manni/parse-error";
|
|
10177
10846
|
var SCHEMA_ERROR_RULE = "manni/schema-error";
|
|
10847
|
+
var SIDECAR_OWNED_RULE = "sidecar:owned/sidecar";
|
|
10848
|
+
var SIDECAR_DUPLICATE_RULE = "sidecar:duplicate/sidecar";
|
|
10178
10849
|
var RESERVED_RULES = {
|
|
10179
10850
|
[PARSE_ERROR_RULE]: "The document's metadata block could not be parsed.",
|
|
10180
|
-
[SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document."
|
|
10851
|
+
[SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document.",
|
|
10852
|
+
[SIDECAR_OWNED_RULE]: "The document carries a key a sidecar manifest owns; the manifest is the only place that key may be set.",
|
|
10853
|
+
[SIDECAR_DUPLICATE_RULE]: "Two documents carry the same value of a sidecar's join field, so one manifest entry matched both."
|
|
10181
10854
|
};
|
|
10182
10855
|
function ruleIdFor(e, frame) {
|
|
10183
10856
|
if (e.schema === SYNTHETIC_SCHEMA) {
|
|
@@ -10205,7 +10878,7 @@ function uriFrame(frame) {
|
|
|
10205
10878
|
}
|
|
10206
10879
|
function artifactUri(file, frame) {
|
|
10207
10880
|
const uri = canonicalFilePath(file, frame);
|
|
10208
|
-
if (uri === "" || uri.startsWith("../") ||
|
|
10881
|
+
if (uri === "" || uri.startsWith("../") || isAbsolute5(uri)) return null;
|
|
10209
10882
|
return uri;
|
|
10210
10883
|
}
|
|
10211
10884
|
function renderSarif(results, opts = {}) {
|
|
@@ -10219,12 +10892,13 @@ function renderSarif(results, opts = {}) {
|
|
|
10219
10892
|
for (const r of results) {
|
|
10220
10893
|
if (r.errors.length === 0) continue;
|
|
10221
10894
|
if (r.file === STDIN_LABEL) continue;
|
|
10222
|
-
const
|
|
10223
|
-
if (uri === null) {
|
|
10224
|
-
dropped += r.errors.length;
|
|
10225
|
-
continue;
|
|
10226
|
-
}
|
|
10895
|
+
const docUri = artifactUri(r.file, pathFrame);
|
|
10227
10896
|
for (const e of r.errors) {
|
|
10897
|
+
const uri = e.file != null ? artifactUri(e.file, pathFrame) : docUri;
|
|
10898
|
+
if (uri === null) {
|
|
10899
|
+
dropped += 1;
|
|
10900
|
+
continue;
|
|
10901
|
+
}
|
|
10228
10902
|
const ruleId = ruleIdFor(e, frame);
|
|
10229
10903
|
if (!rules.has(ruleId)) {
|
|
10230
10904
|
rules.set(ruleId, {
|
|
@@ -10332,7 +11006,7 @@ function renderJunit(results, opts = {}) {
|
|
|
10332
11006
|
}
|
|
10333
11007
|
lines.push(`${open2}>`);
|
|
10334
11008
|
for (const e of r.errors) {
|
|
10335
|
-
const where = e.line != null ? ` (line ${e.line})` : "";
|
|
11009
|
+
const where = e.file != null ? ` (${e.file}${e.line != null ? `:${e.line}` : ""})` : e.line != null ? ` (line ${e.line})` : "";
|
|
10336
11010
|
lines.push(
|
|
10337
11011
|
` <failure${attr("type", ruleIdFor(e, opts.frame))}${attr(
|
|
10338
11012
|
"message",
|
|
@@ -10346,7 +11020,7 @@ function renderJunit(results, opts = {}) {
|
|
|
10346
11020
|
return lines.join("\n");
|
|
10347
11021
|
}
|
|
10348
11022
|
|
|
10349
|
-
// src/
|
|
11023
|
+
// src/shared/color.ts
|
|
10350
11024
|
import pc from "picocolors";
|
|
10351
11025
|
function palette(enabled) {
|
|
10352
11026
|
return pc.createColors(enabled);
|
|
@@ -10358,7 +11032,7 @@ function shouldColor(opts) {
|
|
|
10358
11032
|
return Boolean(opts.isTTY);
|
|
10359
11033
|
}
|
|
10360
11034
|
|
|
10361
|
-
// src/
|
|
11035
|
+
// src/shared/github.ts
|
|
10362
11036
|
function escapeWorkflowCommandMessage(message) {
|
|
10363
11037
|
return message.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
10364
11038
|
}
|
|
@@ -10441,7 +11115,7 @@ function renderPretty(results, summary, opts = {}) {
|
|
|
10441
11115
|
}
|
|
10442
11116
|
lines.push(`${c.red("\u2717")} ${r.file}${forgiven}`);
|
|
10443
11117
|
for (const e of r.errors) {
|
|
10444
|
-
const loc = e.line != null ? c.dim(` (line ${e.line})`) : "";
|
|
11118
|
+
const loc = e.file != null ? c.dim(` (${e.file}${e.line != null ? `:${e.line}` : ""})`) : e.line != null ? c.dim(` (line ${e.line})`) : "";
|
|
10445
11119
|
lines.push(
|
|
10446
11120
|
` ${c.cyan(fieldLabel(e.instancePath))} ${e.message}${loc} ${c.dim(
|
|
10447
11121
|
`[${e.schema}]`
|
|
@@ -10465,7 +11139,7 @@ function renderGithub(results) {
|
|
|
10465
11139
|
const lines = [];
|
|
10466
11140
|
for (const r of results) {
|
|
10467
11141
|
for (const e of r.errors) {
|
|
10468
|
-
const params = [`file=${escapeWorkflowCommandProperty(r.file)}`];
|
|
11142
|
+
const params = [`file=${escapeWorkflowCommandProperty(e.file ?? r.file)}`];
|
|
10469
11143
|
if (e.line != null) params.push(`line=${e.line}`);
|
|
10470
11144
|
if (e.col != null) params.push(`col=${e.col}`);
|
|
10471
11145
|
const msg = escapeWorkflowCommandMessage(
|
|
@@ -10739,9 +11413,15 @@ function cellFrom(value) {
|
|
|
10739
11413
|
|
|
10740
11414
|
export {
|
|
10741
11415
|
package_default,
|
|
11416
|
+
palette,
|
|
11417
|
+
shouldColor,
|
|
11418
|
+
ToolError,
|
|
10742
11419
|
programName,
|
|
10743
11420
|
setProgramName,
|
|
10744
|
-
|
|
11421
|
+
findConfigFile,
|
|
11422
|
+
readConfigFile,
|
|
11423
|
+
escapeWorkflowCommandMessage,
|
|
11424
|
+
escapeWorkflowCommandProperty,
|
|
10745
11425
|
DocmetaError,
|
|
10746
11426
|
toJsonText,
|
|
10747
11427
|
writeFileAtomic,
|
|
@@ -10758,17 +11438,32 @@ export {
|
|
|
10758
11438
|
fetchSchemaBytes,
|
|
10759
11439
|
schemaLoadOptions,
|
|
10760
11440
|
loadSchema,
|
|
11441
|
+
DEFAULT_SCHEMAS,
|
|
11442
|
+
schemaEntryRef,
|
|
11443
|
+
collectSchemaPins,
|
|
11444
|
+
rebaseConfigSchemaRefs,
|
|
11445
|
+
resolveSchemaSet,
|
|
11446
|
+
SIDECAR_FETCH_TIMEOUT_MS,
|
|
11447
|
+
SIDECAR_FETCH_MAX_BYTES,
|
|
11448
|
+
sidecarUrlProblem,
|
|
11449
|
+
fetchSidecar,
|
|
10761
11450
|
locateFrontmatter,
|
|
10762
11451
|
frontmatterInnerText,
|
|
10763
11452
|
extractFrontmatter,
|
|
10764
11453
|
applyFrontmatter,
|
|
10765
11454
|
extractorForExtension,
|
|
10766
11455
|
supportedExtensions,
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
11456
|
+
SIDECAR_OWNED_SCHEMA,
|
|
11457
|
+
SIDECAR_DUPLICATE_SCHEMA,
|
|
11458
|
+
SIDECAR_KEYWORD,
|
|
11459
|
+
PATH_JOIN,
|
|
11460
|
+
sidecarJoin,
|
|
11461
|
+
loadSidecars,
|
|
11462
|
+
mergeSidecars,
|
|
11463
|
+
sidecarPointer,
|
|
11464
|
+
orphanEntries,
|
|
11465
|
+
orphanJoins,
|
|
11466
|
+
orphanError,
|
|
10772
11467
|
parseConfig,
|
|
10773
11468
|
loadConfig,
|
|
10774
11469
|
resolveRunConfig,
|
|
@@ -10784,8 +11479,6 @@ export {
|
|
|
10784
11479
|
runInferSchema,
|
|
10785
11480
|
runQuery,
|
|
10786
11481
|
runFill,
|
|
10787
|
-
palette,
|
|
10788
|
-
shouldColor,
|
|
10789
11482
|
renderSarif,
|
|
10790
11483
|
renderJunit,
|
|
10791
11484
|
REPORT_FORMATS,
|
|
@@ -10811,4 +11504,4 @@ export {
|
|
|
10811
11504
|
renderQuery,
|
|
10812
11505
|
renderQueryCsv
|
|
10813
11506
|
};
|
|
10814
|
-
//# sourceMappingURL=chunk-
|
|
11507
|
+
//# sourceMappingURL=chunk-7M2YOXVG.js.map
|