lighthouse 13.5.0 → 13.6.0-dev.20261027
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/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -1
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/autocomplete-valid.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/presentation-role-conflict.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/svg-img-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/agentic/ard-schema.d.ts +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +8 -28
- package/core/config/config.js +1 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +0 -1
- package/core/lib/i18n/i18n.js +17 -11
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +1 -2
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
package/third-party/ard/ard.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export type ParsedMediaType = [string, Array<[string, string]>];
|
|
1
2
|
export type ValidationError = {
|
|
2
3
|
element: string;
|
|
3
4
|
message: string;
|
|
4
5
|
};
|
|
6
|
+
export const ARD_VERSION: "0.91 (f073b1e)";
|
|
5
7
|
/**
|
|
6
8
|
* @typedef {{
|
|
7
9
|
* element: string,
|
|
@@ -13,6 +15,8 @@ export class ConformanceTester {
|
|
|
13
15
|
errors: ValidationError[];
|
|
14
16
|
/** @type {ValidationError[]} */
|
|
15
17
|
warnings: ValidationError[];
|
|
18
|
+
/** @type {ValidationError[]} */
|
|
19
|
+
infos: ValidationError[];
|
|
16
20
|
/**
|
|
17
21
|
* @param {string} message
|
|
18
22
|
* @param {string} [element='Root']
|
|
@@ -23,7 +27,15 @@ export class ConformanceTester {
|
|
|
23
27
|
* @param {string} [element='Root']
|
|
24
28
|
*/
|
|
25
29
|
add_warning(message: string, element?: string): void;
|
|
26
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} message
|
|
32
|
+
* @param {string} [element='Root']
|
|
33
|
+
*/
|
|
34
|
+
add_info(message: string, element?: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Validate the manifest as an ArdManifest, then every entry as an ArdEntry.
|
|
37
|
+
* @param {any} manifest_data
|
|
38
|
+
*/
|
|
27
39
|
run_json_schema_validation(manifest_data: any): boolean;
|
|
28
40
|
/**
|
|
29
41
|
* @param {string} raw_content
|
|
@@ -31,4 +43,9 @@ export class ConformanceTester {
|
|
|
31
43
|
*/
|
|
32
44
|
validate_manifest(raw_content: string, source_label: string): boolean;
|
|
33
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* @param {unknown} media_type
|
|
48
|
+
* @return {['warning'|'info', string]|null}
|
|
49
|
+
*/
|
|
50
|
+
export function classify_media_type(media_type: unknown): ["warning" | "info", string] | null;
|
|
34
51
|
//# sourceMappingURL=ard.d.ts.map
|
package/third-party/ard/ard.js
CHANGED
|
@@ -4,12 +4,222 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview JavaScript port of the manifest validation in the ARD conformance test
|
|
9
|
+
* (`conformance/bin/conformance-test`, ARD v0.91). See README.md for the pinned upstream commit
|
|
10
|
+
* and the Lighthouse-specific adaptations.
|
|
11
|
+
*/
|
|
12
|
+
|
|
7
13
|
import log from 'lighthouse-logger';
|
|
8
|
-
import
|
|
14
|
+
import {ArdManifest as validateArdManifest, ArdEntry as validateArdEntry} from './schema-validator.js';
|
|
15
|
+
|
|
16
|
+
const ARD_VERSION = '0.91 (f073b1e)';
|
|
9
17
|
|
|
10
18
|
// Strict URN Regex matching urn:air:<publisher>:<namespace>:<agent-name>
|
|
11
19
|
const URN_REGEX = /^urn:air:([a-zA-Z0-9.-]+)(?::([a-zA-Z0-9._:-]+))?:([a-zA-Z0-9._-]+)$/;
|
|
12
20
|
|
|
21
|
+
// RFC 6838 restricted-name for type/subtype plus RFC 9110 parameters.
|
|
22
|
+
const RESTRICTED_NAME = '[0-9A-Za-z][0-9A-Za-z!#$&^_.+\\-]{0,126}';
|
|
23
|
+
const TOKEN = "[!#$%&'*+\\-.^_`|~0-9A-Za-z]+";
|
|
24
|
+
const QUOTED_STRING = '"(?:[\\t !#-\\[\\]-~]|\\\\[\\t !-~])*"';
|
|
25
|
+
const MEDIA_TYPE_REGEX = new RegExp(
|
|
26
|
+
`^(?<type>${RESTRICTED_NAME})/(?<subtype>${RESTRICTED_NAME})` +
|
|
27
|
+
`(?<parameters>(?:[ \\t]*;[ \\t]*${TOKEN}[ \\t]*=[ \\t]*(?:${TOKEN}|${QUOTED_STRING}))*)$`
|
|
28
|
+
);
|
|
29
|
+
const MEDIA_TYPE_PARAMETER_REGEX = new RegExp(
|
|
30
|
+
`[ \\t]*;[ \\t]*(?<name>${TOKEN})[ \\t]*=[ \\t]*` +
|
|
31
|
+
`(?<value>${TOKEN}|${QUOTED_STRING})`,
|
|
32
|
+
'g'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {[string, Array<[string, string]>]} ParsedMediaType
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} media_type
|
|
41
|
+
* @return {ParsedMediaType|null}
|
|
42
|
+
*/
|
|
43
|
+
function parse_media_type(media_type) {
|
|
44
|
+
const match = MEDIA_TYPE_REGEX.exec(media_type);
|
|
45
|
+
if (!match?.groups) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const base_media_type =
|
|
50
|
+
`${match.groups.type.toLowerCase()}/${match.groups.subtype.toLowerCase()}`;
|
|
51
|
+
/** @type {Array<[string, string]>} */
|
|
52
|
+
const parameters = [...match.groups.parameters.matchAll(MEDIA_TYPE_PARAMETER_REGEX)]
|
|
53
|
+
.map(parameter => /** @type {[string, string]} */ ([
|
|
54
|
+
/** @type {Record<string, string>} */ (parameter.groups).name.toLowerCase(),
|
|
55
|
+
/** @type {Record<string, string>} */ (parameter.groups).value,
|
|
56
|
+
]))
|
|
57
|
+
// Python sorts (name, value) tuples by code point.
|
|
58
|
+
.sort((a, b) => {
|
|
59
|
+
if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
|
|
60
|
+
if (a[1] !== b[1]) return a[1] < b[1] ? -1 : 1;
|
|
61
|
+
return 0;
|
|
62
|
+
});
|
|
63
|
+
const parameter_names = parameters.map(([name]) => name);
|
|
64
|
+
if (parameter_names.length !== new Set(parameter_names).size) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return [base_media_type, parameters];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @param {ParsedMediaType} parsed */
|
|
71
|
+
const mediaTypeKey = parsed => JSON.stringify(parsed);
|
|
72
|
+
|
|
73
|
+
const STANDARD_MEDIA_TYPES = [
|
|
74
|
+
"application/ai-catalog+json",
|
|
75
|
+
"application/agent-card+json",
|
|
76
|
+
"application/a2a-agent-card+json",
|
|
77
|
+
"application/mcp-server-card+json",
|
|
78
|
+
"application/agent-skills+json",
|
|
79
|
+
"application/agent-skills+md",
|
|
80
|
+
"application/agent-skills+zip",
|
|
81
|
+
"application/agent-skills+gzip",
|
|
82
|
+
'text/markdown; profile="urn:air:agent-skills"',
|
|
83
|
+
"application/agent-plugins+zip",
|
|
84
|
+
"application/agent-plugins+gzip",
|
|
85
|
+
"application/ai-registry",
|
|
86
|
+
"application/ai-registry+json",
|
|
87
|
+
];
|
|
88
|
+
const STANDARD_MEDIA_TYPES_PARSED = STANDARD_MEDIA_TYPES.map(media_type =>
|
|
89
|
+
/** @type {ParsedMediaType} */ (parse_media_type(media_type)));
|
|
90
|
+
const STANDARD_MEDIA_TYPE_KEYS = new Set(STANDARD_MEDIA_TYPES_PARSED.map(mediaTypeKey));
|
|
91
|
+
const STANDARD_BASE_MEDIA_TYPES = new Set(STANDARD_MEDIA_TYPES_PARSED.map(([base]) => base));
|
|
92
|
+
/** @type {Map<string, Map<string, string>>} */
|
|
93
|
+
const STANDARD_PARAMETERS_BY_BASE = new Map(STANDARD_MEDIA_TYPES_PARSED.map(
|
|
94
|
+
([base, parameters]) => [base, new Map(parameters)]));
|
|
95
|
+
/** @type {Record<string, string>} */
|
|
96
|
+
const DEPRECATED_MEDIA_TYPES = {
|
|
97
|
+
"application/mcp-server+json": "application/mcp-server-card+json",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Python's `repr()` of the list of standard media types, to keep messages identical to upstream.
|
|
102
|
+
*/
|
|
103
|
+
const STANDARD_MEDIA_TYPES_REPR = `[${STANDARD_MEDIA_TYPES.map(t => `'${t}'`).join(', ')}]`;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Name of the Python type a JSON value would parse to, for messages matching upstream.
|
|
107
|
+
* @param {unknown} value
|
|
108
|
+
* @return {string}
|
|
109
|
+
*/
|
|
110
|
+
function python_type_name(value) {
|
|
111
|
+
if (value === null) return 'NoneType';
|
|
112
|
+
if (Array.isArray(value)) return 'list';
|
|
113
|
+
if (typeof value === 'boolean') return 'bool';
|
|
114
|
+
if (typeof value === 'number') return Number.isInteger(value) ? 'int' : 'float';
|
|
115
|
+
if (typeof value === 'object') return 'dict';
|
|
116
|
+
return typeof value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {unknown} media_type
|
|
121
|
+
* @return {['warning'|'info', string]|null}
|
|
122
|
+
*/
|
|
123
|
+
function classify_media_type(media_type) {
|
|
124
|
+
if (typeof media_type !== 'string') {
|
|
125
|
+
return [
|
|
126
|
+
"warning",
|
|
127
|
+
`Media type must be a string; got ${python_type_name(media_type)}.`,
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parsed_media_type = parse_media_type(media_type);
|
|
132
|
+
if (parsed_media_type === null) {
|
|
133
|
+
return [
|
|
134
|
+
"warning",
|
|
135
|
+
`Media type '${media_type}' is not a valid IANA media type. ` +
|
|
136
|
+
"Expected 'type/subtype' with optional parameters.",
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const [base_media_type, parameters] = parsed_media_type;
|
|
141
|
+
if (STANDARD_MEDIA_TYPE_KEYS.has(mediaTypeKey(parsed_media_type))) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const replacement = DEPRECATED_MEDIA_TYPES[base_media_type];
|
|
146
|
+
if (replacement !== undefined) {
|
|
147
|
+
return [
|
|
148
|
+
"warning",
|
|
149
|
+
`Media type '${media_type}' was renamed by ADR-0008. ` +
|
|
150
|
+
`Use '${replacement}'.`,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const expected_parameters = STANDARD_PARAMETERS_BY_BASE.get(base_media_type);
|
|
155
|
+
if (STANDARD_BASE_MEDIA_TYPES.has(base_media_type) && expected_parameters) {
|
|
156
|
+
const actual_parameters = new Map(parameters);
|
|
157
|
+
const missing_parameters = [...expected_parameters]
|
|
158
|
+
.filter(([name, value]) => actual_parameters.get(name) !== value)
|
|
159
|
+
.map(([name, value]) => `${name}=${value}`);
|
|
160
|
+
const unrecognized_parameters = parameters
|
|
161
|
+
.filter(([name, value]) => expected_parameters.get(name) !== value)
|
|
162
|
+
.map(([name]) => name);
|
|
163
|
+
const differences = [];
|
|
164
|
+
if (missing_parameters.length) {
|
|
165
|
+
differences.push(
|
|
166
|
+
"missing required parameters: " + missing_parameters.join(", ")
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (unrecognized_parameters.length) {
|
|
170
|
+
differences.push(
|
|
171
|
+
"unrecognized parameters: " + unrecognized_parameters.join(", ")
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return [
|
|
175
|
+
"warning",
|
|
176
|
+
`Media type '${media_type}' is based on standard discovery type ` +
|
|
177
|
+
`'${base_media_type}' but has ` +
|
|
178
|
+
`${differences.join('; ')}.`,
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (base_media_type.startsWith("application/")) {
|
|
183
|
+
return [
|
|
184
|
+
"info",
|
|
185
|
+
`Media type '${media_type}' is a valid application extension media type. ` +
|
|
186
|
+
"ARD permits extension types without core registration.",
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return [
|
|
191
|
+
"warning",
|
|
192
|
+
`Media type '${media_type}' is not one of standard discovery types: ` +
|
|
193
|
+
`${STANDARD_MEDIA_TYPES_REPR}.`,
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {unknown} value
|
|
199
|
+
* @return {value is Record<string, any>}
|
|
200
|
+
*/
|
|
201
|
+
function is_object(value) {
|
|
202
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Errors reported inside a oneOf/anyOf branch or a `not` are restated by the combinator itself.
|
|
206
|
+
const COMBINATOR_BRANCH_REGEX = /\/(?:oneOf|anyOf)\/\d+(?:\/|$)|\/not(?:\/|$)/;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Formats the most relevant Ajv error as `<message> at path '<path>'`.
|
|
210
|
+
* @param {Array<any>|null|undefined} errors
|
|
211
|
+
* @return {string}
|
|
212
|
+
*/
|
|
213
|
+
function format_schema_error(errors) {
|
|
214
|
+
const e = errors?.find(err => !COMBINATOR_BRANCH_REGEX.test(err.schemaPath)) || errors?.[0];
|
|
215
|
+
const propertyPath = e?.instancePath ? e.instancePath.replace(/^\//, '').replace(/\//g, '.') : 'root';
|
|
216
|
+
let msg = e?.message || 'unknown schema error';
|
|
217
|
+
if (e?.keyword === 'required') {
|
|
218
|
+
msg = `'${e.params.missingProperty}' is a required property`;
|
|
219
|
+
}
|
|
220
|
+
return `${msg} at path '${propertyPath}'`;
|
|
221
|
+
}
|
|
222
|
+
|
|
13
223
|
/**
|
|
14
224
|
* @typedef {{
|
|
15
225
|
* element: string,
|
|
@@ -23,6 +233,8 @@ class ConformanceTester {
|
|
|
23
233
|
this.errors = [];
|
|
24
234
|
/** @type {ValidationError[]} */
|
|
25
235
|
this.warnings = [];
|
|
236
|
+
/** @type {ValidationError[]} */
|
|
237
|
+
this.infos = [];
|
|
26
238
|
}
|
|
27
239
|
|
|
28
240
|
/**
|
|
@@ -43,29 +255,53 @@ class ConformanceTester {
|
|
|
43
255
|
log.verbose('ARD', `Validation Warning [${element}]: ${message}`);
|
|
44
256
|
}
|
|
45
257
|
|
|
46
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* @param {string} message
|
|
260
|
+
* @param {string} [element='Root']
|
|
261
|
+
*/
|
|
262
|
+
add_info(message, element = 'Root') {
|
|
263
|
+
this.infos.push({element, message});
|
|
264
|
+
log.verbose('ARD', `Validation Info [${element}]: ${message}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validate the manifest as an ArdManifest, then every entry as an ArdEntry.
|
|
269
|
+
* @param {any} manifest_data
|
|
270
|
+
*/
|
|
47
271
|
run_json_schema_validation(manifest_data) {
|
|
272
|
+
let ok = true;
|
|
48
273
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (valid) {
|
|
52
|
-
log.verbose('ARD', 'Strict JSON Schema validation passed.');
|
|
53
|
-
return true;
|
|
274
|
+
if (validateArdManifest(manifest_data)) {
|
|
275
|
+
log.verbose('ARD', 'Manifest validates against ArdManifest.');
|
|
54
276
|
} else {
|
|
55
|
-
const errors = /** @type {any} */ (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let msg = e?.message || 'unknown schema error';
|
|
59
|
-
if (e?.keyword === 'required') {
|
|
60
|
-
msg = `'${e.params.missingProperty}' is a required property`;
|
|
61
|
-
}
|
|
62
|
-
this.add_error(`JSON Schema Validation Failed: ${msg} at path '${propertyPath}'`, 'Root');
|
|
63
|
-
return false;
|
|
277
|
+
const errors = /** @type {any} */ (validateArdManifest).errors;
|
|
278
|
+
this.add_error(`ArdManifest validation failed: ${format_schema_error(errors)}`, 'Root');
|
|
279
|
+
ok = false;
|
|
64
280
|
}
|
|
65
281
|
} catch (e) {
|
|
66
282
|
this.add_warning(`Failed to run JSON Schema validator: ${e}`, 'Root');
|
|
67
283
|
return true;
|
|
68
284
|
}
|
|
285
|
+
|
|
286
|
+
const entries = is_object(manifest_data) ? manifest_data["entries"] : undefined;
|
|
287
|
+
if (Array.isArray(entries)) {
|
|
288
|
+
let bad = 0;
|
|
289
|
+
entries.forEach((entry, idx) => {
|
|
290
|
+
const label = is_object(entry) ?
|
|
291
|
+
String(entry["displayName"] || entry["identifier"] || `Entry #${idx}`) :
|
|
292
|
+
`Entry #${idx}`;
|
|
293
|
+
if (!validateArdEntry(entry)) {
|
|
294
|
+
const errors = /** @type {any} */ (validateArdEntry).errors;
|
|
295
|
+
this.add_error(`ArdEntry validation failed: ${format_schema_error(errors)}`, label);
|
|
296
|
+
bad += 1;
|
|
297
|
+
ok = false;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
if (!bad) {
|
|
301
|
+
log.verbose('ARD', `All ${entries.length} entries validate against ArdEntry.`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return ok;
|
|
69
305
|
}
|
|
70
306
|
|
|
71
307
|
/**
|
|
@@ -91,14 +327,18 @@ class ConformanceTester {
|
|
|
91
327
|
// 3. Custom Semantic and Protocol-Specific Validation
|
|
92
328
|
log.verbose('ARD', 'Running custom semantic checks...');
|
|
93
329
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
330
|
+
// Upstream assumes a JSON object here; treat anything else as an empty root so the
|
|
331
|
+
// schema error above is accompanied by the missing 'entries' error instead of a crash.
|
|
332
|
+
const root = is_object(data) ? data : {};
|
|
333
|
+
|
|
334
|
+
// ARD requires only 'entries' at the top level (§5.1). Other members are
|
|
335
|
+
// transport-defined and ignored, so their presence is noted, never an error.
|
|
336
|
+
const extra_roots = Object.keys(root).filter(k => k !== "entries");
|
|
337
|
+
if (extra_roots.length) {
|
|
338
|
+
log.verbose('ARD', `Top-level members ignored by ARD (transport-defined): ${extra_roots.sort().join(', ')}.`);
|
|
99
339
|
}
|
|
100
340
|
|
|
101
|
-
const entries =
|
|
341
|
+
const entries = root["entries"];
|
|
102
342
|
if (entries === undefined || entries === null) {
|
|
103
343
|
this.add_error("Missing required 'entries' array.", 'Root');
|
|
104
344
|
return false;
|
|
@@ -108,8 +348,10 @@ class ConformanceTester {
|
|
|
108
348
|
}
|
|
109
349
|
|
|
110
350
|
log.verbose('ARD', `Found ${entries.length} entries to validate.`);
|
|
111
|
-
entries.forEach((
|
|
112
|
-
|
|
351
|
+
entries.forEach((raw_entry, idx) => {
|
|
352
|
+
// Upstream assumes each entry is a JSON object; treat anything else as empty.
|
|
353
|
+
const entry = is_object(raw_entry) ? raw_entry : {};
|
|
354
|
+
const label = String(entry["displayName"] || entry["identifier"] || `Entry #${idx}`);
|
|
113
355
|
|
|
114
356
|
// Required properties
|
|
115
357
|
const ident = entry["identifier"];
|
|
@@ -136,35 +378,30 @@ class ConformanceTester {
|
|
|
136
378
|
if (!media_type) {
|
|
137
379
|
this.add_error("Missing required 'type' (mediaType).", label);
|
|
138
380
|
} else {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"application/ai-registry",
|
|
148
|
-
"application/ai-registry+json"
|
|
149
|
-
];
|
|
150
|
-
if (!valid_types.includes(media_type)) {
|
|
151
|
-
this.add_warning(`Media type '${media_type}' is not one of standard discovery types: ${valid_types}.`, label);
|
|
381
|
+
const diagnostic = classify_media_type(media_type);
|
|
382
|
+
if (diagnostic !== null) {
|
|
383
|
+
const [severity, message] = diagnostic;
|
|
384
|
+
if (severity === "info") {
|
|
385
|
+
this.add_info(message, label);
|
|
386
|
+
} else {
|
|
387
|
+
this.add_warning(message, label);
|
|
388
|
+
}
|
|
152
389
|
}
|
|
153
390
|
}
|
|
154
391
|
|
|
155
392
|
// Strict Value-or-Reference checks
|
|
156
|
-
const has_url =
|
|
157
|
-
const has_data =
|
|
393
|
+
const has_url = "url" in entry;
|
|
394
|
+
const has_data = "data" in entry;
|
|
158
395
|
if (has_url && has_data) {
|
|
159
396
|
this.add_error("Constraint violation: both 'url' and 'data' are provided. MUST provide exactly one.", label);
|
|
160
397
|
} else if (!has_url && !has_data) {
|
|
161
398
|
this.add_error("Constraint violation: neither 'url' nor 'data' is provided. MUST provide exactly one.", label);
|
|
162
399
|
}
|
|
163
400
|
|
|
164
|
-
// Custom constraints for representativeQueries
|
|
401
|
+
// Custom constraints for representativeQueries (§4.2, §D.2)
|
|
165
402
|
const queries = entry["representativeQueries"];
|
|
166
403
|
if (queries === undefined || queries === null) {
|
|
167
|
-
this.add_warning("
|
|
404
|
+
this.add_warning("No 'representativeQueries'. The semantic index is built from this term, so the entry will not be found by search — it is a valid catalog entry but not a discoverable ARD entry.", label);
|
|
168
405
|
} else if (!Array.isArray(queries)) {
|
|
169
406
|
this.add_error("'representativeQueries' must be an array of strings.", label);
|
|
170
407
|
} else {
|
|
@@ -181,7 +418,7 @@ class ConformanceTester {
|
|
|
181
418
|
// Progressive trust checks
|
|
182
419
|
const trust = entry["trustManifest"];
|
|
183
420
|
if (trust !== undefined && trust !== null) {
|
|
184
|
-
if (
|
|
421
|
+
if (!is_object(trust)) {
|
|
185
422
|
this.add_error("'trustManifest' must be a JSON object.", label);
|
|
186
423
|
} else {
|
|
187
424
|
const trust_id = trust["identity"];
|
|
@@ -193,12 +430,12 @@ class ConformanceTester {
|
|
|
193
430
|
});
|
|
194
431
|
|
|
195
432
|
// Top-level deprecated property check
|
|
196
|
-
if ("collections" in
|
|
197
|
-
this.
|
|
433
|
+
if ("collections" in root) {
|
|
434
|
+
this.add_warning("Found 'collections' at root. Top-level collections were removed; ARD ignores unrecognized top-level members, so this does not invalidate the manifest, but hierarchies should be modeled inside 'entries'.", 'Root');
|
|
198
435
|
}
|
|
199
436
|
|
|
200
437
|
return this.errors.length === 0;
|
|
201
438
|
}
|
|
202
439
|
}
|
|
203
440
|
|
|
204
|
-
export { ConformanceTester };
|
|
441
|
+
export { ARD_VERSION, ConformanceTester, classify_media_type };
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function ArdManifest(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
|
|
2
2
|
instancePath?: string | undefined;
|
|
3
3
|
rootData?: any;
|
|
4
4
|
dynamicAnchors?: {} | undefined;
|
|
5
5
|
}): boolean;
|
|
6
|
-
export namespace
|
|
6
|
+
export namespace ArdManifest {
|
|
7
7
|
namespace evaluated {
|
|
8
8
|
let props: boolean;
|
|
9
9
|
let dynamicProps: boolean;
|
|
10
10
|
let dynamicItems: boolean;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export
|
|
14
|
-
declare function validate20(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
|
|
13
|
+
export function ArdEntry(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
|
|
15
14
|
instancePath?: string | undefined;
|
|
16
15
|
rootData?: any;
|
|
17
16
|
dynamicAnchors?: {} | undefined;
|
|
18
17
|
}): boolean;
|
|
19
|
-
|
|
18
|
+
export namespace ArdEntry {
|
|
19
|
+
export namespace evaluated_1 {
|
|
20
|
+
let props_1: boolean;
|
|
21
|
+
export { props_1 as props };
|
|
22
|
+
let dynamicProps_1: boolean;
|
|
23
|
+
export { dynamicProps_1 as dynamicProps };
|
|
24
|
+
let dynamicItems_1: boolean;
|
|
25
|
+
export { dynamicItems_1 as dynamicItems };
|
|
26
|
+
}
|
|
27
|
+
export { evaluated_1 as evaluated };
|
|
28
|
+
}
|
|
20
29
|
//# sourceMappingURL=schema-validator.d.ts.map
|