rfphub-validate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs ADDED
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var standard = require('@rfp-hub/standard');
5
+ var addFormats = require('ajv-formats');
6
+ var Ajv2020 = require('ajv/dist/2020.js');
7
+ var fs = require('fs');
8
+ var path = require('path');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var addFormats__default = /*#__PURE__*/_interopDefault(addFormats);
13
+ var Ajv2020__default = /*#__PURE__*/_interopDefault(Ajv2020);
14
+
15
+ // src/checks/types.ts
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+
20
+ // src/checks/currency.ts
21
+ var milestoneAmountWithoutCurrency = {
22
+ code: "milestone-amount-without-currency",
23
+ entryPhrase: "carry a milestone amount with no funding.currency to denominate it",
24
+ run(entry) {
25
+ const milestones = entry.milestones;
26
+ if (!Array.isArray(milestones)) return [];
27
+ const funding = entry.funding;
28
+ const currency = isRecord(funding) ? funding.currency : void 0;
29
+ if (typeof currency === "string" && currency.length > 0) return [];
30
+ const out = [];
31
+ milestones.forEach((milestone, i) => {
32
+ if (!isRecord(milestone)) return;
33
+ if (typeof milestone.amount !== "number") return;
34
+ out.push({
35
+ code: this.code,
36
+ instancePath: `/milestones/${i}/amount`,
37
+ message: `milestone amount ${milestone.amount} has no funding.currency to denominate it; milestone amounts must follow the top-level envelope currency`
38
+ });
39
+ });
40
+ return out;
41
+ }
42
+ };
43
+ var shortList = (name) => standard.activeValues(name).join(", ");
44
+ var unregisteredEligibilityKey = {
45
+ code: "unregistered-eligibility-key",
46
+ entryPhrase: "use an unregistered eligibility key",
47
+ run(entry) {
48
+ const eligibility = entry.eligibility;
49
+ if (!isRecord(eligibility)) return [];
50
+ return Object.keys(eligibility).filter((key) => !standard.isRegistered("eligibility-keys", key)).map((key) => ({
51
+ code: this.code,
52
+ instancePath: `/eligibility/${key}`,
53
+ message: `eligibility key '${key}' is not registered; conventional keys are ${shortList("eligibility-keys")}`
54
+ }));
55
+ }
56
+ };
57
+ var unregisteredDeadlineLabel = {
58
+ code: "unregistered-deadline-label",
59
+ entryPhrase: "use an unregistered deadline label",
60
+ run(entry) {
61
+ const deadlines = entry.deadlines;
62
+ if (!Array.isArray(deadlines)) return [];
63
+ const out = [];
64
+ deadlines.forEach((deadline, i) => {
65
+ if (!isRecord(deadline)) return;
66
+ const label2 = deadline.label;
67
+ if (typeof label2 !== "string" || label2.length === 0) return;
68
+ if (standard.isRegistered("deadline-labels", label2)) return;
69
+ out.push({
70
+ code: this.code,
71
+ instancePath: `/deadlines/${i}/label`,
72
+ message: `deadline label '${label2}' is not registered; conventional labels are ${shortList("deadline-labels")}`
73
+ });
74
+ });
75
+ return out;
76
+ }
77
+ };
78
+ var unregisteredProgramModel = {
79
+ code: "unregistered-program-model",
80
+ entryPhrase: "use an unregistered grant.programModel",
81
+ run(entry) {
82
+ const grant = entry.grant;
83
+ if (!isRecord(grant)) return [];
84
+ const model = grant.programModel;
85
+ if (typeof model !== "string" || model.length === 0) return [];
86
+ if (standard.isRegistered("program-models", model)) return [];
87
+ return [
88
+ {
89
+ code: this.code,
90
+ instancePath: "/grant/programModel",
91
+ message: `grant.programModel '${model}' is not registered; conventional values are ${shortList("program-models")}`
92
+ }
93
+ ];
94
+ }
95
+ };
96
+
97
+ // src/checks/index.ts
98
+ var checks = [
99
+ unregisteredEligibilityKey,
100
+ unregisteredDeadlineLabel,
101
+ unregisteredProgramModel,
102
+ milestoneAmountWithoutCurrency
103
+ ];
104
+ function runChecks(data) {
105
+ if (!isRecord(data)) return [];
106
+ return checks.flatMap((check) => check.run(data));
107
+ }
108
+ function entryPhrase(code) {
109
+ return checks.find((c) => c.code === code)?.entryPhrase ?? `raise ${code}`;
110
+ }
111
+ var TYPE_BLOCKS = (() => {
112
+ const props = standard.opportunitySchema.properties;
113
+ const values = props?.fundingType?.enum;
114
+ return Array.isArray(values) ? values : [];
115
+ })();
116
+ function isRecord2(v) {
117
+ return typeof v === "object" && v !== null && !Array.isArray(v);
118
+ }
119
+ function isRedundantIfWrapper(e) {
120
+ return e.keyword === "if" && e.params.failingKeyword != null;
121
+ }
122
+ function explainNot(e, data) {
123
+ if (e.keyword !== "not") return void 0;
124
+ if (!/^#\/allOf\/\d+\/then\/not$/.test(e.schemaPath)) return void 0;
125
+ const declared = isRecord2(data) && typeof data.fundingType === "string" ? data.fundingType : null;
126
+ const extras = isRecord2(data) ? TYPE_BLOCKS.filter((k) => k !== declared && Object.hasOwn(data, k)) : [];
127
+ const offending = extras.length > 0 ? `: ${extras.map((k) => `'${k}'`).join(", ")}` : "";
128
+ const expected = declared ? ` Only the '${declared}' block may be present.` : "";
129
+ return `carries a type block that does not match fundingType${offending}.${expected}`;
130
+ }
131
+ function humanizeError(e, data) {
132
+ const where = e.instancePath?.length ? e.instancePath : "(root)";
133
+ const notMessage = explainNot(e, data);
134
+ if (notMessage) return `${where} ${notMessage}`;
135
+ let msg = e.message ?? "is invalid";
136
+ if (e.keyword === "additionalProperties") {
137
+ const { additionalProperty } = e.params;
138
+ msg += `: '${additionalProperty}'`;
139
+ } else if (e.keyword === "enum") {
140
+ const { allowedValues } = e.params;
141
+ if (allowedValues) msg += `: ${allowedValues.join(", ")}`;
142
+ } else if (e.keyword === "const") {
143
+ const { allowedValue } = e.params;
144
+ if (allowedValue !== void 0) msg += `: ${JSON.stringify(allowedValue)}`;
145
+ }
146
+ return `${where} ${msg}`;
147
+ }
148
+ function humanizeErrors(errors, data) {
149
+ const meaningful = errors.filter((e) => !isRedundantIfWrapper(e));
150
+ const kept = meaningful.length > 0 ? meaningful : errors;
151
+ return kept.map((e) => humanizeError(e, data));
152
+ }
153
+
154
+ // src/validator.ts
155
+ var ANNOTATION_KEYWORDS = ["x-stability", "x-since", "x-deprecated"];
156
+ function createValidator(schema = standard.opportunitySchema) {
157
+ const ajv = new Ajv2020__default.default({ allErrors: true, strict: true, strictRequired: false });
158
+ addFormats__default.default(ajv);
159
+ for (const keyword of ANNOTATION_KEYWORDS) ajv.addKeyword(keyword);
160
+ return ajv.compile(schema);
161
+ }
162
+ var _default;
163
+ function defaultValidator() {
164
+ if (!_default) _default = createValidator();
165
+ return _default;
166
+ }
167
+ function resolveValidator(opts) {
168
+ if (opts.validator) return opts.validator;
169
+ if (opts.spec && opts.spec !== standard.SPEC_VERSION) {
170
+ throw new Error(`unsupported spec '${opts.spec}' (this build ships ${standard.SPEC_VERSION})`);
171
+ }
172
+ return defaultValidator();
173
+ }
174
+ function validateOpportunity(data, opts = {}) {
175
+ const validate = resolveValidator(opts);
176
+ const valid = validate(data);
177
+ return {
178
+ valid,
179
+ errors: valid ? [] : validate.errors ?? [],
180
+ warnings: opts.checks === false ? [] : runChecks(data)
181
+ };
182
+ }
183
+ var HELP = `rfphub-validate \u2014 validate funding opportunities against the RFP Hub Standard
184
+
185
+ Usage:
186
+ rfphub-validate [options] <file|dir|->...
187
+
188
+ Inputs:
189
+ Each input may be a JSON file, a directory (all *.json in it are validated),
190
+ or '-' for stdin. Each JSON document may be a single opportunity object or an
191
+ array of opportunity objects.
192
+
193
+ Two tiers are reported. SCHEMA ERRORS are hard conformance failures. ADVISORY WARNINGS
194
+ cover what the schema deliberately leaves open \u2014 unregistered eligibility keys, deadline
195
+ labels and grant.programModel values, and milestone amounts with no envelope currency to
196
+ denominate them. Warnings never make a document non-conformant unless you pass --strict.
197
+
198
+ Options:
199
+ --spec <version> Standard version to validate against (default: ${standard.SPEC_VERSION})
200
+ --list-specs List bundled spec versions and exit
201
+ --json Emit a machine-readable JSON report
202
+ --strict Treat advisory warnings as failures (exit 1)
203
+ -q, --quiet Only print failures, warnings and the summary
204
+ -h, --help Show this help
205
+
206
+ Exit codes:
207
+ 0 all entries valid
208
+ 1 one or more entries invalid (or, with --strict, any advisory warning)
209
+ 2 usage / IO / parse error
210
+
211
+ Examples:
212
+ rfphub-validate opportunity.json
213
+ rfphub-validate ./exports/
214
+ rfphub-validate --strict --json ./exports/
215
+ cat opportunity.json | rfphub-validate -`;
216
+ function parseArgs(argv) {
217
+ const opts = {
218
+ spec: standard.SPEC_VERSION,
219
+ quiet: false,
220
+ json: false,
221
+ strict: false,
222
+ help: false,
223
+ listSpecs: false,
224
+ stdin: false,
225
+ files: []
226
+ };
227
+ for (let i = 0; i < argv.length; i++) {
228
+ const a = argv[i];
229
+ if (a === "-h" || a === "--help") opts.help = true;
230
+ else if (a === "-q" || a === "--quiet") opts.quiet = true;
231
+ else if (a === "--json") opts.json = true;
232
+ else if (a === "--strict") opts.strict = true;
233
+ else if (a === "--list-specs") opts.listSpecs = true;
234
+ else if (a === "--spec") {
235
+ const next = argv[++i];
236
+ if (next === void 0) throw new Error("--spec requires a value");
237
+ opts.spec = next;
238
+ } else if (a.startsWith("--spec=")) opts.spec = a.slice("--spec=".length);
239
+ else if (a === "-") opts.stdin = true;
240
+ else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
241
+ else opts.files.push(a);
242
+ }
243
+ return opts;
244
+ }
245
+
246
+ // src/cli/reporter.ts
247
+ function label(r) {
248
+ return `${r.source}${r.count > 1 ? ` [${r.index}]` : ""}${r.id ? ` ${r.id}` : ""}`;
249
+ }
250
+ function warningCountsByCode(results) {
251
+ const counts = /* @__PURE__ */ new Map();
252
+ for (const r of results) {
253
+ for (const code of new Set(r.result.warnings.map((w) => w.code))) {
254
+ counts.set(code, (counts.get(code) ?? 0) + 1);
255
+ }
256
+ }
257
+ return counts;
258
+ }
259
+ var TextReporter = class {
260
+ constructor(quiet = false) {
261
+ this.quiet = quiet;
262
+ }
263
+ quiet;
264
+ report(results) {
265
+ for (const r of results) {
266
+ const hasWarnings = r.result.warnings.length > 0;
267
+ if (r.result.valid) {
268
+ if (!this.quiet || hasWarnings) {
269
+ process.stdout.write(`${hasWarnings ? "! WARN" : "\u2713 PASS"} ${label(r)}
270
+ `);
271
+ }
272
+ } else {
273
+ process.stdout.write(`\u2717 FAIL ${label(r)}
274
+ `);
275
+ for (const line of humanizeErrors(r.result.errors, r.data)) {
276
+ process.stdout.write(` - ${line}
277
+ `);
278
+ }
279
+ }
280
+ if (hasWarnings) {
281
+ for (const w of r.result.warnings) {
282
+ process.stdout.write(` ~ ${w.instancePath || "(root)"} ${w.message}
283
+ `);
284
+ }
285
+ }
286
+ }
287
+ const passed = results.filter((r) => r.result.valid).length;
288
+ process.stdout.write(`
289
+ ${passed} passed, ${results.length - passed} failed
290
+ `);
291
+ const counts = warningCountsByCode(results);
292
+ if (counts.size > 0) {
293
+ process.stdout.write("\nAdvisory (does not affect conformance; --strict to enforce):\n");
294
+ for (const [code, n] of counts) {
295
+ process.stdout.write(` ${n} of ${results.length} entries ${entryPhrase(code)}
296
+ `);
297
+ }
298
+ }
299
+ }
300
+ };
301
+ var JsonReporter = class {
302
+ constructor(spec) {
303
+ this.spec = spec;
304
+ }
305
+ spec;
306
+ report(results) {
307
+ const passed = results.filter((r) => r.result.valid).length;
308
+ const payload = {
309
+ spec: this.spec,
310
+ total: results.length,
311
+ passed,
312
+ failed: results.length - passed,
313
+ warned: results.filter((r) => r.result.warnings.length > 0).length,
314
+ warningsByCode: Object.fromEntries(warningCountsByCode(results)),
315
+ results: results.map((r) => ({
316
+ source: r.source,
317
+ index: r.index,
318
+ count: r.count,
319
+ id: r.id,
320
+ fundingType: r.fundingType,
321
+ valid: r.result.valid,
322
+ errors: r.result.valid ? [] : humanizeErrors(r.result.errors, r.data),
323
+ warnings: r.result.warnings
324
+ }))
325
+ };
326
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}
327
+ `);
328
+ }
329
+ };
330
+ function expandInputs(targets) {
331
+ const out = [];
332
+ for (const target of targets) {
333
+ const stat = fs.statSync(target);
334
+ if (stat.isDirectory()) {
335
+ for (const name of fs.readdirSync(target).filter((n) => n.endsWith(".json")).sort()) {
336
+ out.push(path.join(target, name));
337
+ }
338
+ } else {
339
+ out.push(target);
340
+ }
341
+ }
342
+ return out;
343
+ }
344
+ function toEntries(parsed) {
345
+ return Array.isArray(parsed) ? parsed : [parsed];
346
+ }
347
+ function readEntries(file) {
348
+ return toEntries(JSON.parse(fs.readFileSync(file, "utf8")));
349
+ }
350
+ function readStdin() {
351
+ let raw = "";
352
+ try {
353
+ raw = fs.readFileSync(0, "utf8");
354
+ } catch {
355
+ raw = "";
356
+ }
357
+ if (!raw.trim()) {
358
+ throw new Error("no input; pass file(s)/dir(s) or pipe JSON to stdin (see --help)");
359
+ }
360
+ return toEntries(JSON.parse(raw));
361
+ }
362
+
363
+ // src/cli/run.ts
364
+ function asRecord(data) {
365
+ return data && typeof data === "object" ? data : {};
366
+ }
367
+ function run(argv) {
368
+ let opts;
369
+ try {
370
+ opts = parseArgs(argv);
371
+ } catch (e) {
372
+ process.stderr.write(`${e.message}
373
+ `);
374
+ return 2;
375
+ }
376
+ if (opts.help) {
377
+ process.stdout.write(`${HELP}
378
+ `);
379
+ return 0;
380
+ }
381
+ if (opts.listSpecs) {
382
+ process.stdout.write(`${standard.SPEC_VERSION}
383
+ `);
384
+ return 0;
385
+ }
386
+ let validator;
387
+ try {
388
+ if (opts.spec !== standard.SPEC_VERSION) {
389
+ throw new Error(`unsupported spec (this build ships ${standard.SPEC_VERSION})`);
390
+ }
391
+ validator = createValidator();
392
+ } catch (e) {
393
+ process.stderr.write(`Cannot load spec '${opts.spec}': ${e.message}
394
+ `);
395
+ return 2;
396
+ }
397
+ let ioError = false;
398
+ const inputs = [];
399
+ if (opts.stdin || opts.files.length === 0) {
400
+ try {
401
+ inputs.push({ source: "<stdin>", entries: readStdin() });
402
+ } catch (e) {
403
+ process.stderr.write(`<stdin>: ${e.message}
404
+ `);
405
+ return 2;
406
+ }
407
+ } else {
408
+ for (const target of opts.files) {
409
+ let files;
410
+ try {
411
+ files = expandInputs([target]);
412
+ } catch (e) {
413
+ process.stderr.write(`${target}: ${e.message}
414
+ `);
415
+ ioError = true;
416
+ continue;
417
+ }
418
+ for (const file of files) {
419
+ try {
420
+ inputs.push({ source: file, entries: readEntries(file) });
421
+ } catch (e) {
422
+ process.stderr.write(`${file}: invalid JSON: ${e.message}
423
+ `);
424
+ ioError = true;
425
+ }
426
+ }
427
+ }
428
+ }
429
+ const results = [];
430
+ for (const { source, entries } of inputs) {
431
+ entries.forEach((data, index) => {
432
+ const obj = asRecord(data);
433
+ results.push({
434
+ source,
435
+ index,
436
+ count: entries.length,
437
+ id: typeof obj.id === "string" ? obj.id : void 0,
438
+ fundingType: typeof obj.fundingType === "string" ? obj.fundingType : void 0,
439
+ data,
440
+ result: validateOpportunity(data, { validator })
441
+ });
442
+ });
443
+ }
444
+ const reporter = opts.json ? new JsonReporter(opts.spec) : new TextReporter(opts.quiet);
445
+ reporter.report(results);
446
+ if (results.some((r) => !r.result.valid)) return 1;
447
+ if (opts.strict && results.some((r) => r.result.warnings.length > 0)) return 1;
448
+ if (ioError) return 2;
449
+ return 0;
450
+ }
451
+
452
+ // src/cli/cli.ts
453
+ process.exit(run(process.argv.slice(2)));
454
+ //# sourceMappingURL=cli.cjs.map
455
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/checks/types.ts","../src/checks/currency.ts","../src/checks/registry.ts","../src/checks/index.ts","../src/errors.ts","../src/validator.ts","../src/cli/args.ts","../src/cli/reporter.ts","../src/cli/sources.ts","../src/cli/run.ts","../src/cli/cli.ts"],"names":["activeValues","isRegistered","label","opportunitySchema","isRecord","Ajv2020","addFormats","SPEC_VERSION","statSync","readdirSync","join","readFileSync"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * The advisory tier.\n *\n * Schema errors and advisory warnings are deliberately different things. The schema stays\n * permissive — open key→value eligibility, free-text deadline labels, an open programModel\n * list — because a closed enum built from one publisher's vocabulary would force every other\n * publisher into it. That permissiveness is what makes the registries load-bearing: nothing\n * would ever notice drift if the only signal were pass/fail.\n *\n * So: a document that raises warnings is still CONFORMANT. Warnings are quality signal,\n * reported separately, and only `--strict` turns them into a failing exit code.\n */\nexport interface Warning {\n /** Stable machine-readable identifier for the check that fired. */\n code: string;\n /** JSON-Pointer-ish path to the offending value, in the same shape ajv uses. */\n instancePath: string;\n /** One-line human-readable explanation, naming the offending value. */\n message: string;\n}\n\nexport interface Check {\n code: string;\n /**\n * Verb phrase completing \"N of M entries …\", for count-phrased text output.\n * e.g. \"use an unregistered eligibility key\".\n */\n entryPhrase: string;\n run(entry: Record<string, unknown>): Warning[];\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { type Check, type Warning, isRecord } from \"./types.js\";\n\n/**\n * A milestone amount MUST be denominated in the top-level `funding.currency` — a stated rule\n * of the standard, not a soft convention. It is also schema-unenforceable: the two values live\n * in different objects, and JSON Schema cannot express a dependency across them. Warning at\n * ingest is the entire enforcement mechanism the rule has, which is why it lives here.\n */\nexport const milestoneAmountWithoutCurrency: Check = {\n code: \"milestone-amount-without-currency\",\n entryPhrase: \"carry a milestone amount with no funding.currency to denominate it\",\n run(entry) {\n const milestones = entry.milestones;\n if (!Array.isArray(milestones)) return [];\n\n const funding = entry.funding;\n const currency = isRecord(funding) ? funding.currency : undefined;\n if (typeof currency === \"string\" && currency.length > 0) return [];\n\n const out: Warning[] = [];\n milestones.forEach((milestone, i) => {\n if (!isRecord(milestone)) return;\n if (typeof milestone.amount !== \"number\") return;\n out.push({\n code: this.code,\n instancePath: `/milestones/${i}/amount`,\n message: `milestone amount ${milestone.amount} has no funding.currency to denominate it; milestone amounts must follow the top-level envelope currency`,\n });\n });\n return out;\n },\n};\n","import { activeValues, isRegistered } from \"@rfp-hub/standard\";\nimport { type Check, type Warning, isRecord } from \"./types.js\";\n\nconst shortList = (name: Parameters<typeof activeValues>[0]) => activeValues(name).join(\", \");\n\n/**\n * `eligibility` is an open map on purpose: publishers choose their own keys. The cost is that\n * two publishers writing `stage` and `projectStage` produce two facets nobody can compare.\n * The registry is the mitigation, and this check is what makes it visible.\n */\nexport const unregisteredEligibilityKey: Check = {\n code: \"unregistered-eligibility-key\",\n entryPhrase: \"use an unregistered eligibility key\",\n run(entry) {\n const eligibility = entry.eligibility;\n if (!isRecord(eligibility)) return [];\n return Object.keys(eligibility)\n .filter((key) => !isRegistered(\"eligibility-keys\", key))\n .map((key) => ({\n code: this.code,\n instancePath: `/eligibility/${key}`,\n message: `eligibility key '${key}' is not registered; conventional keys are ${shortList(\"eligibility-keys\")}`,\n }));\n },\n};\n\n/**\n * Since every per-type date folds into `deadlines[]`, the label is the ONLY thing separating\n * \"when must I apply by?\" from \"when does the event start?\". An unregistered label is a\n * deadline a consumer cannot route on.\n */\nexport const unregisteredDeadlineLabel: Check = {\n code: \"unregistered-deadline-label\",\n entryPhrase: \"use an unregistered deadline label\",\n run(entry) {\n const deadlines = entry.deadlines;\n if (!Array.isArray(deadlines)) return [];\n const out: Warning[] = [];\n deadlines.forEach((deadline, i) => {\n if (!isRecord(deadline)) return;\n const label = deadline.label;\n if (typeof label !== \"string\" || label.length === 0) return;\n if (isRegistered(\"deadline-labels\", label)) return;\n out.push({\n code: this.code,\n instancePath: `/deadlines/${i}/label`,\n message: `deadline label '${label}' is not registered; conventional labels are ${shortList(\"deadline-labels\")}`,\n });\n });\n return out;\n },\n};\n\n/**\n * `grant.programModel` is an open string because it came from one publisher's taxonomy and a\n * closed enum would have imposed that taxonomy on everyone else. The registry keeps the\n * common cases comparable without closing the field.\n */\nexport const unregisteredProgramModel: Check = {\n code: \"unregistered-program-model\",\n entryPhrase: \"use an unregistered grant.programModel\",\n run(entry) {\n const grant = entry.grant;\n if (!isRecord(grant)) return [];\n const model = grant.programModel;\n if (typeof model !== \"string\" || model.length === 0) return [];\n if (isRegistered(\"program-models\", model)) return [];\n return [\n {\n code: this.code,\n instancePath: \"/grant/programModel\",\n message: `grant.programModel '${model}' is not registered; conventional values are ${shortList(\"program-models\")}`,\n },\n ];\n },\n};\n","import { milestoneAmountWithoutCurrency } from \"./currency.js\";\nimport {\n unregisteredDeadlineLabel,\n unregisteredEligibilityKey,\n unregisteredProgramModel,\n} from \"./registry.js\";\nimport { type Check, type Warning, isRecord } from \"./types.js\";\n\n/**\n * The advisory checks, in report order. Each one covers something the schema deliberately\n * leaves open — see ./types.ts for why the two tiers are separate.\n */\nexport const checks: readonly Check[] = [\n unregisteredEligibilityKey,\n unregisteredDeadlineLabel,\n unregisteredProgramModel,\n milestoneAmountWithoutCurrency,\n];\n\n/** Run every advisory check against one entry. Never throws; non-objects yield no warnings. */\nexport function runChecks(data: unknown): Warning[] {\n if (!isRecord(data)) return [];\n return checks.flatMap((check) => check.run(data));\n}\n\n/** The count-phrase for a code, for \"N of M entries <phrase>\" output. */\nexport function entryPhrase(code: string): string {\n return checks.find((c) => c.code === code)?.entryPhrase ?? `raise ${code}`;\n}\n\nexport type { Check, Warning };\nexport {\n milestoneAmountWithoutCurrency,\n unregisteredDeadlineLabel,\n unregisteredEligibilityKey,\n unregisteredProgramModel,\n};\n","import { opportunitySchema } from \"@rfp-hub/standard\";\nimport type { ErrorObject } from \"ajv\";\n\n/**\n * The type-block keys, which by the standard's own invariant are exactly the `fundingType`\n * values. Read from the schema rather than hardcoded, so a seventh type could never make this\n * message silently wrong.\n */\nconst TYPE_BLOCKS: readonly string[] = (() => {\n const props = (opportunitySchema as { properties?: Record<string, { enum?: unknown }> })\n .properties;\n const values = props?.fundingType?.enum;\n return Array.isArray(values) ? (values as string[]) : [];\n})();\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * ajv reports a failed `if`/`then` twice: once for the constraint that actually failed, and\n * once for the `if` wrapper (\"must match \\\"then\\\" schema\"), which names no rule and adds no\n * information. Drop the wrapper — unless it is all we have.\n */\nfunction isRedundantIfWrapper(e: ErrorObject): boolean {\n return e.keyword === \"if\" && (e.params as { failingKeyword?: string }).failingKeyword != null;\n}\n\n/**\n * `not` produces \"must NOT be valid\", which is true and useless. Every `not` in this schema is\n * the one-block-per-fundingType rule, so say that instead — and name the offending block when\n * the instance is available.\n */\nfunction explainNot(e: ErrorObject, data: unknown): string | undefined {\n if (e.keyword !== \"not\") return undefined;\n if (!/^#\\/allOf\\/\\d+\\/then\\/not$/.test(e.schemaPath)) return undefined;\n\n const declared = isRecord(data) && typeof data.fundingType === \"string\" ? data.fundingType : null;\n const extras = isRecord(data)\n ? TYPE_BLOCKS.filter((k) => k !== declared && Object.hasOwn(data, k))\n : [];\n\n const offending = extras.length > 0 ? `: ${extras.map((k) => `'${k}'`).join(\", \")}` : \"\";\n const expected = declared ? ` Only the '${declared}' block may be present.` : \"\";\n return `carries a type block that does not match fundingType${offending}.${expected}`;\n}\n\n/** Render a single ajv error as a concise, human-readable line naming the rule that failed. */\nexport function humanizeError(e: ErrorObject, data?: unknown): string {\n const where = e.instancePath?.length ? e.instancePath : \"(root)\";\n\n const notMessage = explainNot(e, data);\n if (notMessage) return `${where} ${notMessage}`;\n\n let msg = e.message ?? \"is invalid\";\n // ajv's \"required\" message already names the property; only augment where it doesn't.\n if (e.keyword === \"additionalProperties\") {\n const { additionalProperty } = e.params as { additionalProperty: string };\n msg += `: '${additionalProperty}'`;\n } else if (e.keyword === \"enum\") {\n const { allowedValues } = e.params as { allowedValues?: unknown[] };\n if (allowedValues) msg += `: ${allowedValues.join(\", \")}`;\n } else if (e.keyword === \"const\") {\n const { allowedValue } = e.params as { allowedValue?: unknown };\n if (allowedValue !== undefined) msg += `: ${JSON.stringify(allowedValue)}`;\n }\n return `${where} ${msg}`;\n}\n\n/**\n * Render a list of ajv errors as human-readable lines.\n *\n * Pass the validated instance when you have it: it lets the one-block-per-fundingType rule name\n * the block that should not be there, instead of ajv's bare \"must NOT be valid\".\n */\nexport function humanizeErrors(errors: readonly ErrorObject[], data?: unknown): string[] {\n const meaningful = errors.filter((e) => !isRedundantIfWrapper(e));\n const kept = meaningful.length > 0 ? meaningful : errors;\n return kept.map((e) => humanizeError(e, data));\n}\n","import { type Opportunity, SPEC_VERSION, opportunitySchema } from \"@rfp-hub/standard\";\nimport addFormats from \"ajv-formats\";\nimport Ajv2020, { type ErrorObject, type ValidateFunction } from \"ajv/dist/2020.js\";\nimport { type Warning, runChecks } from \"./checks/index.js\";\nimport { humanizeErrors } from \"./errors.js\";\n\nexport interface ValidationResult {\n /** Schema conformance. Advisory warnings never affect this. */\n valid: boolean;\n /** Hard schema violations. A document with any of these is not conformant. */\n errors: ErrorObject[];\n /**\n * Advisory findings from the check tier — quality signal about things the schema\n * deliberately leaves open (unregistered vocabulary values, cross-object rules JSON Schema\n * cannot express). A conformant document may still carry warnings.\n */\n warnings: Warning[];\n}\n\nexport interface ValidateOptions {\n /** Spec version to validate against. Only the bundled version is supported. */\n spec?: string;\n /** Inject a pre-compiled validator (e.g. to validate against a custom schema). */\n validator?: ValidateFunction;\n /** Run the advisory check tier. Default true. */\n checks?: boolean;\n}\n\n/** Annotation keywords the standard uses. Declared so ajv's strict mode accepts them. */\nconst ANNOTATION_KEYWORDS = [\"x-stability\", \"x-since\", \"x-deprecated\"];\n\n/**\n * Compile an ajv validator. Uses the SAME configuration the standard is authored\n * against: draft 2020-12, strict mode, `strictRequired` off (so the conditional\n * type-block pattern — `opportunity[opportunity.fundingType]` — is permitted).\n */\nexport function createValidator(\n schema: Record<string, unknown> = opportunitySchema as Record<string, unknown>,\n): ValidateFunction {\n const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });\n addFormats(ajv);\n for (const keyword of ANNOTATION_KEYWORDS) ajv.addKeyword(keyword);\n return ajv.compile(schema);\n}\n\nlet _default: ValidateFunction | undefined;\nfunction defaultValidator(): ValidateFunction {\n if (!_default) _default = createValidator();\n return _default;\n}\n\nfunction resolveValidator(opts: ValidateOptions): ValidateFunction {\n if (opts.validator) return opts.validator;\n if (opts.spec && opts.spec !== SPEC_VERSION) {\n throw new Error(`unsupported spec '${opts.spec}' (this build ships ${SPEC_VERSION})`);\n }\n return defaultValidator();\n}\n\n/** Validate arbitrary data against the RFP Hub Standard, plus the advisory check tier. */\nexport function validateOpportunity(data: unknown, opts: ValidateOptions = {}): ValidationResult {\n const validate = resolveValidator(opts);\n const valid = validate(data);\n return {\n valid,\n errors: valid ? [] : (validate.errors ?? []),\n warnings: opts.checks === false ? [] : runChecks(data),\n };\n}\n\n/** Assert that data is a valid Opportunity, narrowing its type. Throws otherwise. */\nexport function assertOpportunity(\n data: unknown,\n opts: ValidateOptions = {},\n): asserts data is Opportunity {\n const { valid, errors } = validateOpportunity(data, { ...opts, checks: false });\n if (!valid) {\n const summary = humanizeErrors(errors, data).join(\"; \");\n const err = new Error(`invalid opportunity: ${summary}`) as Error & { errors?: ErrorObject[] };\n err.errors = errors;\n throw err;\n }\n}\n\nexport { SPEC_VERSION };\n","import { SPEC_VERSION } from \"@rfp-hub/standard\";\n\nexport interface CliOptions {\n spec: string;\n quiet: boolean;\n json: boolean;\n strict: boolean;\n help: boolean;\n listSpecs: boolean;\n stdin: boolean;\n files: string[];\n}\n\nexport const HELP = `rfphub-validate — validate funding opportunities against the RFP Hub Standard\n\nUsage:\n rfphub-validate [options] <file|dir|->...\n\nInputs:\n Each input may be a JSON file, a directory (all *.json in it are validated),\n or '-' for stdin. Each JSON document may be a single opportunity object or an\n array of opportunity objects.\n\nTwo tiers are reported. SCHEMA ERRORS are hard conformance failures. ADVISORY WARNINGS\ncover what the schema deliberately leaves open — unregistered eligibility keys, deadline\nlabels and grant.programModel values, and milestone amounts with no envelope currency to\ndenominate them. Warnings never make a document non-conformant unless you pass --strict.\n\nOptions:\n --spec <version> Standard version to validate against (default: ${SPEC_VERSION})\n --list-specs List bundled spec versions and exit\n --json Emit a machine-readable JSON report\n --strict Treat advisory warnings as failures (exit 1)\n -q, --quiet Only print failures, warnings and the summary\n -h, --help Show this help\n\nExit codes:\n 0 all entries valid\n 1 one or more entries invalid (or, with --strict, any advisory warning)\n 2 usage / IO / parse error\n\nExamples:\n rfphub-validate opportunity.json\n rfphub-validate ./exports/\n rfphub-validate --strict --json ./exports/\n cat opportunity.json | rfphub-validate -`;\n\nexport function parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = {\n spec: SPEC_VERSION,\n quiet: false,\n json: false,\n strict: false,\n help: false,\n listSpecs: false,\n stdin: false,\n files: [],\n };\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i] as string;\n if (a === \"-h\" || a === \"--help\") opts.help = true;\n else if (a === \"-q\" || a === \"--quiet\") opts.quiet = true;\n else if (a === \"--json\") opts.json = true;\n else if (a === \"--strict\") opts.strict = true;\n else if (a === \"--list-specs\") opts.listSpecs = true;\n else if (a === \"--spec\") {\n const next = argv[++i];\n if (next === undefined) throw new Error(\"--spec requires a value\");\n opts.spec = next;\n } else if (a.startsWith(\"--spec=\")) opts.spec = a.slice(\"--spec=\".length);\n else if (a === \"-\") opts.stdin = true;\n else if (a.startsWith(\"-\")) throw new Error(`unknown option: ${a}`);\n else opts.files.push(a);\n }\n return opts;\n}\n","import { entryPhrase } from \"../checks/index.js\";\nimport { humanizeErrors } from \"../errors.js\";\nimport type { ValidationResult } from \"../validator.js\";\n\nexport interface EntryResult {\n source: string;\n index: number;\n count: number;\n id?: string;\n fundingType?: string;\n /** The validated instance. Kept so error rendering can name the offending value. */\n data?: unknown;\n result: ValidationResult;\n}\n\n/** Strategy for rendering validation results — implement to add new output formats. */\nexport interface Reporter {\n report(results: EntryResult[]): void;\n}\n\nfunction label(r: EntryResult): string {\n return `${r.source}${r.count > 1 ? ` [${r.index}]` : \"\"}${r.id ? ` ${r.id}` : \"\"}`;\n}\n\n/**\n * How many ENTRIES (not warnings) each check fired on, in the checks' declared order.\n * Counting entries rather than warnings is what makes the summary actionable — \"3 of 40\n * entries use an unregistered eligibility key\" is a coverage statement; \"11 warnings\" is not.\n */\nfunction warningCountsByCode(results: EntryResult[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const r of results) {\n for (const code of new Set(r.result.warnings.map((w) => w.code))) {\n counts.set(code, (counts.get(code) ?? 0) + 1);\n }\n }\n return counts;\n}\n\nexport class TextReporter implements Reporter {\n constructor(private readonly quiet = false) {}\n\n report(results: EntryResult[]): void {\n for (const r of results) {\n const hasWarnings = r.result.warnings.length > 0;\n if (r.result.valid) {\n if (!this.quiet || hasWarnings) {\n process.stdout.write(`${hasWarnings ? \"! WARN\" : \"✓ PASS\"} ${label(r)}\\n`);\n }\n } else {\n process.stdout.write(`✗ FAIL ${label(r)}\\n`);\n for (const line of humanizeErrors(r.result.errors, r.data)) {\n process.stdout.write(` - ${line}\\n`);\n }\n }\n if (hasWarnings) {\n for (const w of r.result.warnings) {\n process.stdout.write(` ~ ${w.instancePath || \"(root)\"} ${w.message}\\n`);\n }\n }\n }\n\n const passed = results.filter((r) => r.result.valid).length;\n process.stdout.write(`\\n${passed} passed, ${results.length - passed} failed\\n`);\n\n const counts = warningCountsByCode(results);\n if (counts.size > 0) {\n process.stdout.write(\"\\nAdvisory (does not affect conformance; --strict to enforce):\\n\");\n for (const [code, n] of counts) {\n process.stdout.write(` ${n} of ${results.length} entries ${entryPhrase(code)}\\n`);\n }\n }\n }\n}\n\nexport class JsonReporter implements Reporter {\n constructor(private readonly spec: string) {}\n\n report(results: EntryResult[]): void {\n const passed = results.filter((r) => r.result.valid).length;\n const payload = {\n spec: this.spec,\n total: results.length,\n passed,\n failed: results.length - passed,\n warned: results.filter((r) => r.result.warnings.length > 0).length,\n warningsByCode: Object.fromEntries(warningCountsByCode(results)),\n results: results.map((r) => ({\n source: r.source,\n index: r.index,\n count: r.count,\n id: r.id,\n fundingType: r.fundingType,\n valid: r.result.valid,\n errors: r.result.valid ? [] : humanizeErrors(r.result.errors, r.data),\n warnings: r.result.warnings,\n })),\n };\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n }\n}\n","import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Expand inputs (files and directories) into a flat, sorted list of JSON file paths. */\nexport function expandInputs(targets: string[]): string[] {\n const out: string[] = [];\n for (const target of targets) {\n const stat = statSync(target); // throws if missing — caller handles\n if (stat.isDirectory()) {\n for (const name of readdirSync(target)\n .filter((n) => n.endsWith(\".json\"))\n .sort()) {\n out.push(join(target, name));\n }\n } else {\n out.push(target);\n }\n }\n return out;\n}\n\nfunction toEntries(parsed: unknown): unknown[] {\n return Array.isArray(parsed) ? parsed : [parsed];\n}\n\n/** Read a JSON file into a list of entries (single object → one entry; array → many). */\nexport function readEntries(file: string): unknown[] {\n return toEntries(JSON.parse(readFileSync(file, \"utf8\")));\n}\n\n/** Read entries from stdin. Throws if empty or invalid. */\nexport function readStdin(): unknown[] {\n let raw = \"\";\n try {\n raw = readFileSync(0, \"utf8\");\n } catch {\n raw = \"\";\n }\n if (!raw.trim()) {\n throw new Error(\"no input; pass file(s)/dir(s) or pipe JSON to stdin (see --help)\");\n }\n return toEntries(JSON.parse(raw));\n}\n","import { SPEC_VERSION } from \"@rfp-hub/standard\";\nimport type { ValidateFunction } from \"ajv/dist/2020.js\";\nimport { createValidator, validateOpportunity } from \"../validator.js\";\nimport { HELP, parseArgs } from \"./args.js\";\nimport { type EntryResult, JsonReporter, type Reporter, TextReporter } from \"./reporter.js\";\nimport { expandInputs, readEntries, readStdin } from \"./sources.js\";\n\ninterface Source {\n source: string;\n entries: unknown[];\n}\n\nfunction asRecord(data: unknown): Record<string, unknown> {\n return data && typeof data === \"object\" ? (data as Record<string, unknown>) : {};\n}\n\n/** Run the CLI. Returns the process exit code (0 ok, 1 invalid, 2 usage/IO). */\nexport function run(argv: string[]): number {\n let opts: ReturnType<typeof parseArgs>;\n try {\n opts = parseArgs(argv);\n } catch (e) {\n process.stderr.write(`${(e as Error).message}\\n`);\n return 2;\n }\n\n if (opts.help) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (opts.listSpecs) {\n process.stdout.write(`${SPEC_VERSION}\\n`);\n return 0;\n }\n\n let validator: ValidateFunction;\n try {\n if (opts.spec !== SPEC_VERSION) {\n throw new Error(`unsupported spec (this build ships ${SPEC_VERSION})`);\n }\n validator = createValidator();\n } catch (e) {\n process.stderr.write(`Cannot load spec '${opts.spec}': ${(e as Error).message}\\n`);\n return 2;\n }\n\n let ioError = false;\n const inputs: Source[] = [];\n\n if (opts.stdin || opts.files.length === 0) {\n try {\n inputs.push({ source: \"<stdin>\", entries: readStdin() });\n } catch (e) {\n process.stderr.write(`<stdin>: ${(e as Error).message}\\n`);\n return 2;\n }\n } else {\n for (const target of opts.files) {\n let files: string[];\n try {\n files = expandInputs([target]);\n } catch (e) {\n process.stderr.write(`${target}: ${(e as Error).message}\\n`);\n ioError = true;\n continue;\n }\n for (const file of files) {\n try {\n inputs.push({ source: file, entries: readEntries(file) });\n } catch (e) {\n process.stderr.write(`${file}: invalid JSON: ${(e as Error).message}\\n`);\n ioError = true;\n }\n }\n }\n }\n\n const results: EntryResult[] = [];\n for (const { source, entries } of inputs) {\n entries.forEach((data, index) => {\n const obj = asRecord(data);\n results.push({\n source,\n index,\n count: entries.length,\n id: typeof obj.id === \"string\" ? obj.id : undefined,\n fundingType: typeof obj.fundingType === \"string\" ? obj.fundingType : undefined,\n data,\n result: validateOpportunity(data, { validator }),\n });\n });\n }\n\n const reporter: Reporter = opts.json ? new JsonReporter(opts.spec) : new TextReporter(opts.quiet);\n reporter.report(results);\n\n if (results.some((r) => !r.result.valid)) return 1;\n // --strict promotes the advisory tier: quality findings become a failing build.\n if (opts.strict && results.some((r) => r.result.warnings.length > 0)) return 1;\n if (ioError) return 2;\n return 0;\n}\n","#!/usr/bin/env node\nimport { run } from \"./run.js\";\n\nprocess.exit(run(process.argv.slice(2)));\n"]}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node