diff --git a/scripts/lib/validate-utils.mjs b/scripts/lib/validate-utils.mjs index 8375b22f..9a55ac23 100644 --- a/scripts/lib/validate-utils.mjs +++ b/scripts/lib/validate-utils.mjs @@ -271,23 +271,32 @@ export function checkPublishableSourceUrl(errors, path, value) { * Push a "duplicate or missing id" error when `id` is falsy or already in * `seen`, then record `id` in `seen` regardless. * - * validate-case-studies.mjs and validate-radar-reports.mjs each carried an + * validate-case-studies.mjs, validate-radar-reports.mjs, + * validate-metrics.mjs, and validate-launch-metrics.mjs each carried an * independent copy of this per-entry id-dedup check, byte-identical apart - * from the `errors`/`ids` variable names. + * from the `errors`/`ids` variable names and the entry-type noun in the + * message. * * @param {Array} errors - Array to accumulate errors. * @param {string} path - File path or identifier used as the error's path. * @param {unknown} id - Candidate entry id (entry.id, not a display fallback). * @param {Set} seen - Set of ids already encountered; mutated. + * @param {string} [message] - Error message; defaults to the generic form. * @returns {boolean} Whether `id` passed the check. */ -export function checkDuplicateOrMissingId(errors, path, id, seen) { +export function checkDuplicateOrMissingId( + errors, + path, + id, + seen, + message = 'duplicate or missing id', +) { const ok = Boolean(id) && !seen.has(id); if (!ok) { errors.push({ path, severity: 'error', - message: 'duplicate or missing id', + message, }); } seen.add(id); diff --git a/scripts/validate-launch-metrics.mjs b/scripts/validate-launch-metrics.mjs index a64d1418..be81ac47 100644 --- a/scripts/validate-launch-metrics.mjs +++ b/scripts/validate-launch-metrics.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node import { readFileSync } from 'node:fs'; import { + checkDuplicateOrMissingId, checkIsoTimestamp, checkUrl as checkUrlImpl, reportAndExit, @@ -72,13 +73,13 @@ if (signals.length < 3 || signals.length > 5) const ids = new Set(); for (const signal of signals) { const path = signal.id || '(missing id)'; - if (!signal.id || ids.has(signal.id)) - errors.push({ - path, - severity: 'error', - message: 'duplicate or missing signal id', - }); - ids.add(signal.id); + checkDuplicateOrMissingId( + errors, + path, + signal.id, + ids, + 'duplicate or missing signal id', + ); if (!signal.label) errors.push({ path, severity: 'error', message: 'missing label' }); if (!signal.sourceUrl || !signal.collectedAt) diff --git a/scripts/validate-metrics.mjs b/scripts/validate-metrics.mjs index 92a2db36..f9ef039a 100644 --- a/scripts/validate-metrics.mjs +++ b/scripts/validate-metrics.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node import { readFileSync } from 'node:fs'; import { + checkDuplicateOrMissingId, checkIsoTimestamp, checkUrl as checkUrlImpl, reportAndExit, @@ -69,13 +70,13 @@ checkUrl( ); const ids = new Set(); for (const metric of data.metrics || []) { - if (!metric.id || ids.has(metric.id)) - errors.push({ - path: metric.id, - severity: 'error', - message: 'duplicate or missing metric id', - }); - ids.add(metric.id); + checkDuplicateOrMissingId( + errors, + metric.id, + metric.id, + ids, + 'duplicate or missing metric id', + ); if ( metric.value === undefined || metric.value === null || diff --git a/tests/validate-utils.test.mjs b/tests/validate-utils.test.mjs index 8fd2a71c..819ca377 100644 --- a/tests/validate-utils.test.mjs +++ b/tests/validate-utils.test.mjs @@ -616,6 +616,25 @@ test('checkDuplicateOrMissingId reports a missing id and a repeat of one already } }); +test('checkDuplicateOrMissingId uses a caller-supplied message when given one', () => { + const errors = []; + const seen = new Set(); + checkDuplicateOrMissingId( + errors, + 'display-path', + undefined, + seen, + 'duplicate or missing metric id', + ); + assert.deepEqual(errors, [ + { + path: 'display-path', + severity: 'error', + message: 'duplicate or missing metric id', + }, + ]); +}); + test('checkIsoTimestamp accepts a strict ISO 8601 UTC value and pushes no error', () => { const errors = []; const ok = checkIsoTimestamp(