@setsquare/review-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +57 -0
- package/README.md +338 -0
- package/THIRD-PARTY-NOTICES.md +61 -0
- package/dist/autoscan.d.ts +25 -0
- package/dist/autoscan.d.ts.map +1 -0
- package/dist/autoscan.js +332 -0
- package/dist/autoscan.js.map +1 -0
- package/dist/axe.d.ts +96 -0
- package/dist/axe.d.ts.map +1 -0
- package/dist/axe.js +210 -0
- package/dist/axe.js.map +1 -0
- package/dist/bin/setsquare.d.ts +3 -0
- package/dist/bin/setsquare.d.ts.map +1 -0
- package/dist/bin/setsquare.js +21 -0
- package/dist/bin/setsquare.js.map +1 -0
- package/dist/brand.d.ts +84 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +335 -0
- package/dist/brand.js.map +1 -0
- package/dist/checks/dynamic.d.ts +57 -0
- package/dist/checks/dynamic.d.ts.map +1 -0
- package/dist/checks/dynamic.js +184 -0
- package/dist/checks/dynamic.js.map +1 -0
- package/dist/checks/rule-pack.d.ts +34 -0
- package/dist/checks/rule-pack.d.ts.map +1 -0
- package/dist/checks/rule-pack.js +270 -0
- package/dist/checks/rule-pack.js.map +1 -0
- package/dist/checks/versions.d.ts +28 -0
- package/dist/checks/versions.d.ts.map +1 -0
- package/dist/checks/versions.js +45 -0
- package/dist/checks/versions.js.map +1 -0
- package/dist/ci.d.ts +37 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +199 -0
- package/dist/ci.js.map +1 -0
- package/dist/contract.d.ts +334 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +35 -0
- package/dist/contract.js.map +1 -0
- package/dist/dedupe.d.ts +79 -0
- package/dist/dedupe.d.ts.map +1 -0
- package/dist/dedupe.js +102 -0
- package/dist/dedupe.js.map +1 -0
- package/dist/document.d.ts +29 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +94 -0
- package/dist/document.js.map +1 -0
- package/dist/envelope.d.ts +124 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +337 -0
- package/dist/envelope.js.map +1 -0
- package/dist/escape.d.ts +14 -0
- package/dist/escape.d.ts.map +1 -0
- package/dist/escape.js +20 -0
- package/dist/escape.js.map +1 -0
- package/dist/findings.d.ts +47 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +229 -0
- package/dist/findings.js.map +1 -0
- package/dist/fixture.d.ts +7 -0
- package/dist/fixture.d.ts.map +1 -0
- package/dist/fixture.js +237 -0
- package/dist/fixture.js.map +1 -0
- package/dist/group.d.ts +214 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +709 -0
- package/dist/group.js.map +1 -0
- package/dist/html-report.d.ts +68 -0
- package/dist/html-report.d.ts.map +1 -0
- package/dist/html-report.js +649 -0
- package/dist/html-report.js.map +1 -0
- package/dist/human.d.ts +65 -0
- package/dist/human.d.ts.map +1 -0
- package/dist/human.js +111 -0
- package/dist/human.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +65 -0
- package/dist/infer.d.ts.map +1 -0
- package/dist/infer.js +67 -0
- package/dist/infer.js.map +1 -0
- package/dist/insights.d.ts +140 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +483 -0
- package/dist/insights.js.map +1 -0
- package/dist/install.d.ts +8 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +114 -0
- package/dist/install.js.map +1 -0
- package/dist/links.d.ts +45 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +45 -0
- package/dist/links.js.map +1 -0
- package/dist/mask.d.ts +85 -0
- package/dist/mask.d.ts.map +1 -0
- package/dist/mask.js +98 -0
- package/dist/mask.js.map +1 -0
- package/dist/observe.d.ts +51 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +233 -0
- package/dist/observe.js.map +1 -0
- package/dist/options.d.ts +249 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +185 -0
- package/dist/options.js.map +1 -0
- package/dist/output.d.ts +113 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +231 -0
- package/dist/output.js.map +1 -0
- package/dist/perf.d.ts +11 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +150 -0
- package/dist/perf.js.map +1 -0
- package/dist/reading-level.d.ts +73 -0
- package/dist/reading-level.d.ts.map +1 -0
- package/dist/reading-level.js +189 -0
- package/dist/reading-level.js.map +1 -0
- package/dist/reporter.d.ts +94 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +709 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules.d.ts +55 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +77 -0
- package/dist/rules.js.map +1 -0
- package/dist/runtime.d.ts +32 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +67 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sanitise.d.ts +116 -0
- package/dist/sanitise.d.ts.map +1 -0
- package/dist/sanitise.js +247 -0
- package/dist/sanitise.js.map +1 -0
- package/dist/scan.d.ts +60 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +246 -0
- package/dist/scan.js.map +1 -0
- package/dist/screens.d.ts +98 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +301 -0
- package/dist/screens.js.map +1 -0
- package/dist/settle.d.ts +65 -0
- package/dist/settle.d.ts.map +1 -0
- package/dist/settle.js +162 -0
- package/dist/settle.js.map +1 -0
- package/dist/shots.d.ts +109 -0
- package/dist/shots.d.ts.map +1 -0
- package/dist/shots.js +113 -0
- package/dist/shots.js.map +1 -0
- package/dist/style.d.ts +47 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +154 -0
- package/dist/style.js.map +1 -0
- package/dist/suite.d.ts +218 -0
- package/dist/suite.d.ts.map +1 -0
- package/dist/suite.js +219 -0
- package/dist/suite.js.map +1 -0
- package/dist/summary.d.ts +268 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +1308 -0
- package/dist/summary.js.map +1 -0
- package/dist/unscannable.d.ts +75 -0
- package/dist/unscannable.d.ts.map +1 -0
- package/dist/unscannable.js +111 -0
- package/dist/unscannable.js.map +1 -0
- package/dist/versions.d.ts +13 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +39 -0
- package/dist/versions.js.map +1 -0
- package/dist/wcag-map.d.ts +19 -0
- package/dist/wcag-map.d.ts.map +1 -0
- package/dist/wcag-map.js +52 -0
- package/dist/wcag-map.js.map +1 -0
- package/dist/with-setsquare.d.ts +26 -0
- package/dist/with-setsquare.d.ts.map +1 -0
- package/dist/with-setsquare.js +70 -0
- package/dist/with-setsquare.js.map +1 -0
- package/package.json +81 -0
package/dist/document.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { sanitiseLabel } from './sanitise.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hash the title to 16 hex characters.
|
|
5
|
+
*
|
|
6
|
+
* A page title routinely names a person — `Candidate: Jane Doe — Review` — and
|
|
7
|
+
* the finding needs only equality, never the text. Hashing in the worker means
|
|
8
|
+
* the title is never written into a record, an attachment, or Playwright's trace.
|
|
9
|
+
*
|
|
10
|
+
* Stated plainly because it would otherwise be overclaimed: a title is
|
|
11
|
+
* low-entropy text, so this is DATA MINIMISATION, not a privacy guarantee. Anyone
|
|
12
|
+
* holding a candidate list could confirm a guess. What it does buy is that we
|
|
13
|
+
* never store or transmit the string, and the platform never needs to.
|
|
14
|
+
*/
|
|
15
|
+
function hashTitle(title) {
|
|
16
|
+
return createHash('sha256').update(title, 'utf8').digest('hex').slice(0, 16);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read theme and document facts from the live page.
|
|
20
|
+
*
|
|
21
|
+
* Returns `{}` on any failure. Every field is independently optional, and absence
|
|
22
|
+
* always means "not captured", never a negative finding — `documentConsistency`
|
|
23
|
+
* relies on that to keep an uncaptured surface out of `skipLinkAbsent`, which
|
|
24
|
+
* would otherwise manufacture findings out of our own gaps.
|
|
25
|
+
*/
|
|
26
|
+
export async function readDocumentFacts(page) {
|
|
27
|
+
let raw;
|
|
28
|
+
try {
|
|
29
|
+
raw = await page.evaluate(() => {
|
|
30
|
+
const root = document.documentElement;
|
|
31
|
+
const result = {};
|
|
32
|
+
// Our own attribute first: a customer who has told us explicitly should not
|
|
33
|
+
// be second-guessed by a heuristic. `data-theme` is the widespread
|
|
34
|
+
// convention and is what the reference implementation uses.
|
|
35
|
+
const theme = root.getAttribute('data-setsquare-theme') ?? root.getAttribute('data-theme') ?? undefined;
|
|
36
|
+
if (theme)
|
|
37
|
+
result.theme = theme;
|
|
38
|
+
const title = document.title;
|
|
39
|
+
if (typeof title === 'string' && title.length > 0)
|
|
40
|
+
result.title = title;
|
|
41
|
+
const lang = root.getAttribute('lang');
|
|
42
|
+
if (lang)
|
|
43
|
+
result.lang = lang;
|
|
44
|
+
/*
|
|
45
|
+
* A skip link is the first in-page anchor that lands on a real target.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately structural rather than text-matching: looking for the words
|
|
48
|
+
* "skip to content" is English-only and would report every localised site as
|
|
49
|
+
* having no skip link. Only the first few anchors are considered, because a
|
|
50
|
+
* skip link that is not near the top of the document is not doing its job —
|
|
51
|
+
* and `bypass` is axe's rule for the conformance question. This value exists
|
|
52
|
+
* to compare surfaces against each other, not to adjudicate 2.4.1.
|
|
53
|
+
*/
|
|
54
|
+
const anchors = Array.from(document.querySelectorAll('a[href^="#"]')).slice(0, 5);
|
|
55
|
+
result.hasSkipLink = anchors.some((anchor) => {
|
|
56
|
+
const href = anchor.getAttribute('href');
|
|
57
|
+
if (!href || href === '#')
|
|
58
|
+
return false;
|
|
59
|
+
try {
|
|
60
|
+
return document.querySelector(`[id="${CSS.escape(href.slice(1))}"]`) !== null;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return result;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Navigating away mid-read, a closed page, a policy that blocked evaluation.
|
|
71
|
+
// None of these is a finding about the page.
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
const facts = {};
|
|
75
|
+
const theme = sanitiseLabel(raw.theme);
|
|
76
|
+
if (theme)
|
|
77
|
+
facts.theme = theme;
|
|
78
|
+
const document_ = {};
|
|
79
|
+
if (raw.title !== undefined)
|
|
80
|
+
document_.titleHash = hashTitle(raw.title);
|
|
81
|
+
// BCP-47's practical maximum. A `lang` longer than this is not a language tag,
|
|
82
|
+
// so it is dropped rather than truncated into something that looks like one.
|
|
83
|
+
if (raw.lang !== undefined && raw.lang.length <= 35) {
|
|
84
|
+
const lang = sanitiseLabel(raw.lang);
|
|
85
|
+
if (lang)
|
|
86
|
+
document_.lang = lang;
|
|
87
|
+
}
|
|
88
|
+
if (raw.hasSkipLink !== undefined)
|
|
89
|
+
document_.hasSkipLink = raw.hasSkipLink;
|
|
90
|
+
if (Object.keys(document_).length > 0)
|
|
91
|
+
facts.document = document_;
|
|
92
|
+
return facts;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { RunIngestRequest, TestResult } from '@setsquare/schema';
|
|
2
|
+
import type { ScanRecord } from './contract.js';
|
|
3
|
+
import type { SlowStep, TestFailureFacts } from './suite.js';
|
|
4
|
+
import type { Shot } from './shots.js';
|
|
5
|
+
import { UNKNOWN } from './ci.js';
|
|
6
|
+
import { type InferenceConfig } from './infer.js';
|
|
7
|
+
/**
|
|
8
|
+
* Assemble the wire body from what the workers observed.
|
|
9
|
+
*
|
|
10
|
+
* This file is the reporter's half of the seam: everything here is
|
|
11
|
+
* interpretation, and none of it happens in a worker. It produces the ingest
|
|
12
|
+
* request body EXACTLY as `packages/schema` defines it — no local-only fields,
|
|
13
|
+
* no helpful extras. Local facts go to the sidecar. That separation is what makes
|
|
14
|
+
* "the same artifact the platform would have received, minus nothing" a claim the
|
|
15
|
+
* test suite can check rather than a sentence in a README.
|
|
16
|
+
*
|
|
17
|
+
* The envelope has two arrays because a test and a scan are different things with
|
|
18
|
+
* different cardinalities — a test visits many surfaces, or none. Stage 1 of P1a
|
|
19
|
+
* is what proved that: the old single-array shape silently discarded every
|
|
20
|
+
* surface after the first.
|
|
21
|
+
*/
|
|
22
|
+
/** One finished test, as the reporter saw it. */
|
|
23
|
+
export interface TestOutcome {
|
|
24
|
+
testId: string;
|
|
25
|
+
title: string;
|
|
26
|
+
project?: string;
|
|
27
|
+
retry: number;
|
|
28
|
+
status: TestResult['status'];
|
|
29
|
+
durationMs: number;
|
|
30
|
+
records: ScanRecord[];
|
|
31
|
+
/**
|
|
32
|
+
* Which Playwright worker ran this test.
|
|
33
|
+
*
|
|
34
|
+
* Absent when Playwright reported `-1` (a test that never got a worker), which
|
|
35
|
+
* must not become `0` — worker zero is a real worker, and conflating the two
|
|
36
|
+
* would invent an imbalance out of skipped tests.
|
|
37
|
+
*/
|
|
38
|
+
workerIndex?: number;
|
|
39
|
+
/** Why it failed. Absent on a pass, and on a failure whose result said nothing. */
|
|
40
|
+
failure?: TestFailureFacts;
|
|
41
|
+
/**
|
|
42
|
+
* The slowest leaf steps in this test.
|
|
43
|
+
*
|
|
44
|
+
* Local-only, and a REDUCTION rather than the tree: a full step tree is
|
|
45
|
+
* unbounded and the useful artefact is a short list, so the truncation happens
|
|
46
|
+
* in the worker where the tree already exists.
|
|
47
|
+
*/
|
|
48
|
+
slowestSteps?: SlowStep[];
|
|
49
|
+
/**
|
|
50
|
+
* Whether this test's project prepares the suite rather than tests it,
|
|
51
|
+
* resolved against ITS OWN invocation's dependency graph.
|
|
52
|
+
*
|
|
53
|
+
* Set on outcomes that crossed an invocation boundary (run groups). A merged
|
|
54
|
+
* run holds outcomes from several Playwright configs, and a project name that
|
|
55
|
+
* is infrastructure in one config's graph can be a real test project in
|
|
56
|
+
* another's — so the flag must be resolved where the graph is, not against a
|
|
57
|
+
* union of sets that flattens the distinction. Absent means "resolve against
|
|
58
|
+
* `infrastructureProjects`", which is the single-invocation path unchanged.
|
|
59
|
+
*/
|
|
60
|
+
infrastructure?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The inference config of the invocation this outcome came from, for the
|
|
63
|
+
* same reason: surface/persona/theme maps are per-config, and resolving one
|
|
64
|
+
* lane's scans with another lane's maps would mislabel them quietly.
|
|
65
|
+
*/
|
|
66
|
+
inference?: InferenceConfig;
|
|
67
|
+
/**
|
|
68
|
+
* The run-group lane label this outcome came from. Set only by the merge;
|
|
69
|
+
* flows onto the wire result so a consumer can query it without parsing the
|
|
70
|
+
* testId prefix back apart.
|
|
71
|
+
*/
|
|
72
|
+
lane?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Where Playwright put the screenshots this test produced.
|
|
75
|
+
*
|
|
76
|
+
* Paths only — never pixels, and never a file we wrote. Local to the sidecar
|
|
77
|
+
* and the contact sheet, and deliberately absent from the wire envelope: a
|
|
78
|
+
* filesystem path is meaningless to the platform and `run.json` is strict, so
|
|
79
|
+
* `buildEnvelope` reads this field for nothing. It travels through run-group
|
|
80
|
+
* parts, which is what lets a merged sheet show every lane's captures.
|
|
81
|
+
*/
|
|
82
|
+
shots?: Shot[];
|
|
83
|
+
}
|
|
84
|
+
export interface EnvelopeInputs {
|
|
85
|
+
outcomes: TestOutcome[];
|
|
86
|
+
/** Projects that prepare the suite rather than test it — derived, not configured. */
|
|
87
|
+
infrastructureProjects?: ReadonlySet<string>;
|
|
88
|
+
inference?: InferenceConfig;
|
|
89
|
+
sdkVersion: string;
|
|
90
|
+
axeVersion: string;
|
|
91
|
+
branch: string;
|
|
92
|
+
commit: string;
|
|
93
|
+
runSource: 'ci' | 'local';
|
|
94
|
+
ci?: RunIngestRequest['source']['ci'];
|
|
95
|
+
/** Present only when this envelope is a run-group merge. See the schema. */
|
|
96
|
+
runGroup?: RunIngestRequest['source']['runGroup'];
|
|
97
|
+
generatedAt: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Re-exported so the envelope, the reporter and their tests cannot disagree on
|
|
101
|
+
* what 'we could not determine this' looks like.
|
|
102
|
+
*/
|
|
103
|
+
export { UNKNOWN };
|
|
104
|
+
export interface EnvelopeBuild {
|
|
105
|
+
envelope: RunIngestRequest;
|
|
106
|
+
unknowns: string[];
|
|
107
|
+
warnings: string[];
|
|
108
|
+
}
|
|
109
|
+
/** Re-exported so the envelope's own tests and the reporter agree on one value. */
|
|
110
|
+
export declare function buildEnvelope(inputs: EnvelopeInputs): EnvelopeBuild;
|
|
111
|
+
/**
|
|
112
|
+
* Pin the detection engine.
|
|
113
|
+
*
|
|
114
|
+
* Stage 1 digests the ruleset's IDENTITY (the axe-core version), which is
|
|
115
|
+
* reproducible and honest but coarse: it does not change when rules are enabled
|
|
116
|
+
* or disabled. Once scanning exists this digests the actual resolved rule set,
|
|
117
|
+
* which is what "evidence claims are relative to a specific ruleset" requires.
|
|
118
|
+
*
|
|
119
|
+
* Deliberately a real hash of a stated input rather than a placeholder constant.
|
|
120
|
+
* A fake digest that looks like a digest is worse than no digest, because it
|
|
121
|
+
* would be trusted.
|
|
122
|
+
*/
|
|
123
|
+
export declare function rulesetDigest(axeVersion: string): string;
|
|
124
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,gBAAgB,EAAE,UAAU,EAAgB,MAAM,mBAAmB,CAAC;AAQ7F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,YAAY,CAAC;AAGpB;;;;;;;;;;;;;;GAcG;AAEH,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC;IAC1B,EAAE,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,mFAAmF;AAEnF,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,CAyKnE;AA2HD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { FORMAT_VERSION } from '@setsquare/schema/format-version';
|
|
3
|
+
import { MAX_SURFACE_LENGTH, MAX_TEST_ID_LENGTH, MAX_TEST_TITLE_LENGTH, } from '@setsquare/schema/limits';
|
|
4
|
+
import { UNKNOWN } from './ci.js';
|
|
5
|
+
import { dedupeScans } from './dedupe.js';
|
|
6
|
+
import { inferSurface, resolvePersona, resolveTheme, } from './infer.js';
|
|
7
|
+
import { capLength, sanitiseTestText, toUrlPattern } from './sanitise.js';
|
|
8
|
+
/**
|
|
9
|
+
* Re-exported so the envelope, the reporter and their tests cannot disagree on
|
|
10
|
+
* what 'we could not determine this' looks like.
|
|
11
|
+
*/
|
|
12
|
+
export { UNKNOWN };
|
|
13
|
+
/** Re-exported so the envelope's own tests and the reporter agree on one value. */
|
|
14
|
+
export function buildEnvelope(inputs) {
|
|
15
|
+
const unknowns = [];
|
|
16
|
+
const warnings = [];
|
|
17
|
+
if (inputs.branch === UNKNOWN)
|
|
18
|
+
unknowns.push('branch');
|
|
19
|
+
if (inputs.commit === UNKNOWN)
|
|
20
|
+
unknowns.push('commit');
|
|
21
|
+
const results = [];
|
|
22
|
+
const candidates = [];
|
|
23
|
+
/*
|
|
24
|
+
* The unscannable ledger, summed across every page in the run.
|
|
25
|
+
*
|
|
26
|
+
* Aggregated here rather than per record because a route pattern that failed to
|
|
27
|
+
* settle in four different tests is one fact with a count of four, not four
|
|
28
|
+
* facts. Summing is also what makes a merged run group correct — parts carry
|
|
29
|
+
* their own rows and the merge must add them, not concatenate duplicates.
|
|
30
|
+
*/
|
|
31
|
+
const ledger = new Map();
|
|
32
|
+
let unscannedOmitted = 0;
|
|
33
|
+
for (const outcome of inputs.outcomes) {
|
|
34
|
+
results.push(toResult(outcome));
|
|
35
|
+
for (const record of outcome.records) {
|
|
36
|
+
if (record.kind !== 'observation')
|
|
37
|
+
continue;
|
|
38
|
+
unscannedOmitted += record.unscannedOmitted ?? 0;
|
|
39
|
+
for (const row of record.unscanned ?? []) {
|
|
40
|
+
addLedgerRow(ledger, row.urlPattern, row.reason, row.count);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const scan of toScans(outcome, outcome.inference ?? inputs.inference ?? {})) {
|
|
44
|
+
const project = outcome.project ?? '';
|
|
45
|
+
candidates.push({
|
|
46
|
+
scan,
|
|
47
|
+
project,
|
|
48
|
+
infrastructure: outcome.infrastructure ?? inputs.infrastructureProjects?.has(project) ?? false,
|
|
49
|
+
index: candidates.length,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/*
|
|
54
|
+
* Best-wins, keyed on the full context tuple.
|
|
55
|
+
*
|
|
56
|
+
* The reference implementation's warmup project reaches every route in the app
|
|
57
|
+
* FIRST, so a first-wins rule would have filled the grid with evidence from a
|
|
58
|
+
* pass that only ever waited for a heading. See `dedupe.ts`.
|
|
59
|
+
*/
|
|
60
|
+
const { kept: scans, dropped } = dedupeScans(candidates);
|
|
61
|
+
/*
|
|
62
|
+
* Surfaces the suite VISITED but nothing SCANNED.
|
|
63
|
+
*
|
|
64
|
+
* Auto-scan fires when a page settles, and a test that clicks through three
|
|
65
|
+
* routes faster than the quiet window leaves two of them unscanned — the
|
|
66
|
+
* teardown sweep only ever sees the last one. Measured on the fixture suite:
|
|
67
|
+
* the SPA spec visits /spa, /spa/one and /spa/two, and /spa/two is gone.
|
|
68
|
+
*
|
|
69
|
+
* This is the honest limit of a heuristic that must not scan mid-render, and it
|
|
70
|
+
* is reported rather than hidden. A customer looking at a grid missing a surface
|
|
71
|
+
* their suite definitely visits deserves to be told which one and why — the
|
|
72
|
+
* alternative is that they conclude the surface is fine, which is the one thing
|
|
73
|
+
* this product must never let them conclude by accident.
|
|
74
|
+
*
|
|
75
|
+
* The observation records exist for exactly this: the fixture watches every
|
|
76
|
+
* navigation whether or not a scan follows.
|
|
77
|
+
*/
|
|
78
|
+
const scannedPatterns = new Set(scans.map((scan) => scan.context.urlPattern ?? ''));
|
|
79
|
+
const visited = new Set();
|
|
80
|
+
for (const outcome of inputs.outcomes) {
|
|
81
|
+
for (const record of outcome.records) {
|
|
82
|
+
if (record.kind !== 'observation')
|
|
83
|
+
continue;
|
|
84
|
+
for (const navigation of record.observed.navigations) {
|
|
85
|
+
visited.add(toUrlPattern(navigation.url).pattern);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const unscanned = [...visited].filter((pattern) => !scannedPatterns.has(pattern)).sort();
|
|
90
|
+
for (const pattern of unscanned) {
|
|
91
|
+
/*
|
|
92
|
+
* The same fact as the sentence below, as a countable row.
|
|
93
|
+
*
|
|
94
|
+
* Both are kept deliberately: the prose says WHY in a way a figure cannot,
|
|
95
|
+
* and the row is what lets coverage state its own denominator. Dropping the
|
|
96
|
+
* sentence when the ledger arrived would have been a regression in
|
|
97
|
+
* explanation dressed up as progress.
|
|
98
|
+
*/
|
|
99
|
+
addLedgerRow(ledger, pattern, 'settle-timed-out-empty', 1);
|
|
100
|
+
}
|
|
101
|
+
if (unscanned.length > 0) {
|
|
102
|
+
warnings.push(
|
|
103
|
+
// "surface(s) were" is the sound a form makes, not a sentence. This line is
|
|
104
|
+
// read by someone deciding whether their run covered what they think it
|
|
105
|
+
// did, and a tool that cannot conjugate reads as a tool that was not
|
|
106
|
+
// finished.
|
|
107
|
+
`${unscanned.length} ${unscanned.length === 1 ? 'surface was' : 'surfaces were'} ` +
|
|
108
|
+
'visited but never scanned, because the test moved on before the page ' +
|
|
109
|
+
`settled: ${unscanned.slice(0, 10).join(', ')}${unscanned.length > 10 ? ', …' : ''}. ` +
|
|
110
|
+
`${unscanned.length === 1 ? 'It is' : 'They are'} absent from this run rather ` +
|
|
111
|
+
'than reported as clean.');
|
|
112
|
+
}
|
|
113
|
+
/*
|
|
114
|
+
* The ruleset actually used, taken from the scans rather than from the
|
|
115
|
+
* installed package version.
|
|
116
|
+
*
|
|
117
|
+
* These differ in a way that matters. The installed version says which axe is
|
|
118
|
+
* on disk; the digest of a real run says which rules were evaluated inside the
|
|
119
|
+
* browser — which is the thing an evidence claim is relative to. Falling back
|
|
120
|
+
* to the version-only digest happens only when nothing was scanned, and it is
|
|
121
|
+
* honest there because there is no ruleset to describe.
|
|
122
|
+
*/
|
|
123
|
+
const pinned = pinnedRuleset(inputs.outcomes);
|
|
124
|
+
if (pinned && pinned.disagreements > 0) {
|
|
125
|
+
/*
|
|
126
|
+
* Scans within one run disagreeing on the ruleset is not a hypothetical: a
|
|
127
|
+
* sharded run can be produced by runners whose dependencies drifted. The run
|
|
128
|
+
* envelope has one digest, so this is stated out loud rather than resolved by
|
|
129
|
+
* picking a winner quietly — findings from two rulesets are not comparable,
|
|
130
|
+
* and pretending otherwise is exactly the kind of claim this product exists
|
|
131
|
+
* not to make.
|
|
132
|
+
*/
|
|
133
|
+
warnings.push(`Scans in this run used ${pinned.disagreements + 1} different axe rulesets. The run ` +
|
|
134
|
+
`envelope records one, so findings across this run are not strictly comparable. ` +
|
|
135
|
+
`This usually means runners with drifted dependencies.`);
|
|
136
|
+
}
|
|
137
|
+
const envelope = {
|
|
138
|
+
formatVersion: FORMAT_VERSION,
|
|
139
|
+
kind: 'run',
|
|
140
|
+
generatedAt: inputs.generatedAt,
|
|
141
|
+
source: {
|
|
142
|
+
runSource: inputs.runSource,
|
|
143
|
+
// The client never claims canonicality. The server decides, and in the
|
|
144
|
+
// local path there is no server — so this is false, always, and honestly.
|
|
145
|
+
canonical: false,
|
|
146
|
+
branch: inputs.branch,
|
|
147
|
+
commit: inputs.commit,
|
|
148
|
+
sdkVersion: inputs.sdkVersion,
|
|
149
|
+
axeVersion: pinned?.axeVersion ?? inputs.axeVersion,
|
|
150
|
+
rulesetDigest: pinned?.rulesetDigest ?? rulesetDigest(inputs.axeVersion),
|
|
151
|
+
...(inputs.ci ? { ci: inputs.ci } : {}),
|
|
152
|
+
...(inputs.runGroup ? { runGroup: inputs.runGroup } : {}),
|
|
153
|
+
},
|
|
154
|
+
results,
|
|
155
|
+
scans,
|
|
156
|
+
// Absent when nothing went unscanned. An empty array would be a claim that
|
|
157
|
+
// the run reached everything, which is a stronger statement than we have.
|
|
158
|
+
...(ledger.size > 0 ? { unscanned: [...ledger.values()] } : {}),
|
|
159
|
+
};
|
|
160
|
+
if (unscannedOmitted > 0) {
|
|
161
|
+
warnings.push(`${unscannedOmitted} unscannable-surface events were not recorded because the run hit ` +
|
|
162
|
+
`the ledger's row cap. The counts below are a floor, not a total.`);
|
|
163
|
+
}
|
|
164
|
+
return { envelope, unknowns, warnings };
|
|
165
|
+
}
|
|
166
|
+
function toResult(outcome) {
|
|
167
|
+
const result = {
|
|
168
|
+
testId: sanitiseTestText(outcome.testId, MAX_TEST_ID_LENGTH),
|
|
169
|
+
title: sanitiseTestText(outcome.title, MAX_TEST_TITLE_LENGTH),
|
|
170
|
+
// A test runs in exactly one engine, because a Playwright project has one
|
|
171
|
+
// browserName. Its scans inherit it through `testId` rather than repeating it.
|
|
172
|
+
engine: engineOf(outcome),
|
|
173
|
+
retry: outcome.retry,
|
|
174
|
+
status: outcome.status,
|
|
175
|
+
durationMs: Math.max(0, Math.round(outcome.durationMs)),
|
|
176
|
+
};
|
|
177
|
+
if (outcome.project)
|
|
178
|
+
result.project = outcome.project;
|
|
179
|
+
if (outcome.lane)
|
|
180
|
+
result.lane = outcome.lane;
|
|
181
|
+
if (outcome.workerIndex !== undefined)
|
|
182
|
+
result.workerIndex = outcome.workerIndex;
|
|
183
|
+
if (outcome.failure)
|
|
184
|
+
result.failure = outcome.failure;
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Turn a test's scan records into wire-format scans.
|
|
189
|
+
*
|
|
190
|
+
* Both kinds of scan arrive here as `kind: 'scan'` records and differ in one
|
|
191
|
+
* thing: whether the customer named the surface. An explicit call carries a name
|
|
192
|
+
* we could never have worked out; an automatic one carries an empty name and
|
|
193
|
+
* gets the route's own shape instead. The payload always says which, so a grid
|
|
194
|
+
* can be honest about how confident its row labels are.
|
|
195
|
+
*
|
|
196
|
+
* Observations produce NO scan rows. Under the reshaped schema a scan row IS the
|
|
197
|
+
* evidence that scanning happened — a row with no findings means "scanned, found
|
|
198
|
+
* nothing", and no row means "never scanned". Emitting a row for a page the suite
|
|
199
|
+
* merely walked past would assert a clean result for something nothing looked at.
|
|
200
|
+
*/
|
|
201
|
+
/**
|
|
202
|
+
* Add to an aggregated ledger, summing rather than appending.
|
|
203
|
+
*
|
|
204
|
+
* Joined on U+0000, which the contract bans from every string it accepts, so a
|
|
205
|
+
* route pattern containing the separator cannot forge a collision.
|
|
206
|
+
*/
|
|
207
|
+
function addLedgerRow(ledger, urlPattern, reason, count) {
|
|
208
|
+
const key = `${urlPattern}\u0000${reason}`;
|
|
209
|
+
const existing = ledger.get(key);
|
|
210
|
+
if (existing)
|
|
211
|
+
existing.count += count;
|
|
212
|
+
else
|
|
213
|
+
ledger.set(key, { urlPattern, reason, count });
|
|
214
|
+
}
|
|
215
|
+
function toScans(outcome, inference) {
|
|
216
|
+
const scans = [];
|
|
217
|
+
for (const record of outcome.records) {
|
|
218
|
+
if (record.kind !== 'scan')
|
|
219
|
+
continue;
|
|
220
|
+
const { pattern } = toUrlPattern(record.page.url);
|
|
221
|
+
const explicit = record.declared.surface.length > 0;
|
|
222
|
+
const context = {
|
|
223
|
+
project: record.page.project,
|
|
224
|
+
urlPattern: pattern,
|
|
225
|
+
...(record.page.observedTheme ? { observedTheme: record.page.observedTheme } : {}),
|
|
226
|
+
};
|
|
227
|
+
// A declared value always outranks a resolved one: the customer telling us
|
|
228
|
+
// beats us inferring, on every axis, everywhere.
|
|
229
|
+
const theme = record.declared.theme ?? resolveTheme(context, inference);
|
|
230
|
+
const persona = record.declared.persona ?? resolvePersona(context, inference);
|
|
231
|
+
const surface = explicit
|
|
232
|
+
? capLength(record.declared.surface, MAX_SURFACE_LENGTH)
|
|
233
|
+
: inferSurface(pattern, inference);
|
|
234
|
+
const scan = {
|
|
235
|
+
testId: sanitiseTestText(outcome.testId, MAX_TEST_ID_LENGTH),
|
|
236
|
+
visitRef: record.visitRef,
|
|
237
|
+
context: {
|
|
238
|
+
surface,
|
|
239
|
+
surfaceSource: explicit ? 'explicit' : 'inferred',
|
|
240
|
+
urlPattern: pattern,
|
|
241
|
+
},
|
|
242
|
+
// Stated by the scan, never inferred from what it found: a scan with no AAA
|
|
243
|
+
// findings might be an AA scan that never looked.
|
|
244
|
+
wcagTarget: record.wcagTarget,
|
|
245
|
+
findings: record.findings,
|
|
246
|
+
passes: record.passes,
|
|
247
|
+
...(record.inapplicable && record.inapplicable.length > 0
|
|
248
|
+
? { inapplicable: record.inapplicable }
|
|
249
|
+
: {}),
|
|
250
|
+
};
|
|
251
|
+
if (theme)
|
|
252
|
+
scan.context.theme = theme;
|
|
253
|
+
if (persona)
|
|
254
|
+
scan.context.persona = persona;
|
|
255
|
+
if (record.declared.state)
|
|
256
|
+
scan.context.state = record.declared.state;
|
|
257
|
+
if (record.page.viewport)
|
|
258
|
+
scan.context.viewport = record.page.viewport;
|
|
259
|
+
// Only the first scan of a page load carries timings, so this is absent far
|
|
260
|
+
// more often than it is present — which is exactly right.
|
|
261
|
+
if (record.perf)
|
|
262
|
+
scan.perf = record.perf;
|
|
263
|
+
// Same grain as perf, for the same reason: these describe the page LOAD.
|
|
264
|
+
// Absent on later scans of one visit, so one 500 is reported once.
|
|
265
|
+
if (record.runtime)
|
|
266
|
+
scan.runtime = record.runtime;
|
|
267
|
+
if (record.document)
|
|
268
|
+
scan.document = record.document;
|
|
269
|
+
// Absent on explicit scans: the customer chose the moment, so no heuristic
|
|
270
|
+
// ran and there is nothing to be suspicious of.
|
|
271
|
+
if (record.settle)
|
|
272
|
+
scan.settle = record.settle;
|
|
273
|
+
scans.push(scan);
|
|
274
|
+
}
|
|
275
|
+
return scans;
|
|
276
|
+
}
|
|
277
|
+
/** Stable within a run, meaningless outside it — which is all the server needs. */
|
|
278
|
+
function visitRef(testId, atMs) {
|
|
279
|
+
return createHash('sha256').update(`${testId}#${atMs}`, 'utf8').digest('hex').slice(0, 16);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Pin the detection engine.
|
|
283
|
+
*
|
|
284
|
+
* Stage 1 digests the ruleset's IDENTITY (the axe-core version), which is
|
|
285
|
+
* reproducible and honest but coarse: it does not change when rules are enabled
|
|
286
|
+
* or disabled. Once scanning exists this digests the actual resolved rule set,
|
|
287
|
+
* which is what "evidence claims are relative to a specific ruleset" requires.
|
|
288
|
+
*
|
|
289
|
+
* Deliberately a real hash of a stated input rather than a placeholder constant.
|
|
290
|
+
* A fake digest that looks like a digest is worse than no digest, because it
|
|
291
|
+
* would be trusted.
|
|
292
|
+
*/
|
|
293
|
+
export function rulesetDigest(axeVersion) {
|
|
294
|
+
const digest = createHash('sha256').update(`axe-core@${axeVersion}`, 'utf8').digest('hex');
|
|
295
|
+
return `sha256:${digest}`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The engine a test ran in, from whichever record kind is available.
|
|
299
|
+
*
|
|
300
|
+
* A Playwright project has one browserName, so every record for a test agrees;
|
|
301
|
+
* this only needs to find one of them. Defaults to chromium when a test opened no
|
|
302
|
+
* browser at all — the field is required, and for a test with no page the value
|
|
303
|
+
* is a formality rather than a claim about anything.
|
|
304
|
+
*/
|
|
305
|
+
function engineOf(outcome) {
|
|
306
|
+
for (const record of outcome.records) {
|
|
307
|
+
if (record.kind === 'observation')
|
|
308
|
+
return record.observed.engine;
|
|
309
|
+
return record.page.engine;
|
|
310
|
+
}
|
|
311
|
+
return 'chromium';
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The ruleset the run's scans actually used.
|
|
315
|
+
*
|
|
316
|
+
* Returns the first scan's pinning and a count of how many others disagreed with
|
|
317
|
+
* it. Scans are visited in order so the answer is deterministic — two runs of the
|
|
318
|
+
* same suite must produce byte-identical envelopes wherever the inputs match.
|
|
319
|
+
*/
|
|
320
|
+
function pinnedRuleset(outcomes) {
|
|
321
|
+
let pinned;
|
|
322
|
+
let disagreements = 0;
|
|
323
|
+
for (const outcome of outcomes) {
|
|
324
|
+
for (const record of outcome.records) {
|
|
325
|
+
if (record.kind !== 'scan')
|
|
326
|
+
continue;
|
|
327
|
+
if (!pinned) {
|
|
328
|
+
pinned = { axeVersion: record.axeVersion, rulesetDigest: record.rulesetDigest };
|
|
329
|
+
}
|
|
330
|
+
else if (record.rulesetDigest !== pinned.rulesetDigest) {
|
|
331
|
+
disagreements += 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return pinned ? { ...pinned, disagreements } : undefined;
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
|
package/dist/escape.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML escaping, in one place because there is more than one renderer.
|
|
3
|
+
*
|
|
4
|
+
* Both HTML artifacts — the accessibility report and the visual contact sheet —
|
|
5
|
+
* interpolate strings the customer's own page, config and filesystem supplied.
|
|
6
|
+
* Two copies of this function is two chances for one of them to be the weaker
|
|
7
|
+
* copy, and the weaker copy is the one an injected selector finds. The hostile
|
|
8
|
+
* fixture exists to prove a page full of injection attempts renders as inert
|
|
9
|
+
* text; that proof has to cover every sink, so every sink shares this function.
|
|
10
|
+
*
|
|
11
|
+
* `&` goes first, or the escapes escape each other's output.
|
|
12
|
+
*/
|
|
13
|
+
export declare function esc(value: string): string;
|
|
14
|
+
//# sourceMappingURL=escape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzC"}
|
package/dist/escape.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML escaping, in one place because there is more than one renderer.
|
|
3
|
+
*
|
|
4
|
+
* Both HTML artifacts — the accessibility report and the visual contact sheet —
|
|
5
|
+
* interpolate strings the customer's own page, config and filesystem supplied.
|
|
6
|
+
* Two copies of this function is two chances for one of them to be the weaker
|
|
7
|
+
* copy, and the weaker copy is the one an injected selector finds. The hostile
|
|
8
|
+
* fixture exists to prove a page full of injection attempts renders as inert
|
|
9
|
+
* text; that proof has to cover every sink, so every sink shares this function.
|
|
10
|
+
*
|
|
11
|
+
* `&` goes first, or the escapes escape each other's output.
|
|
12
|
+
*/
|
|
13
|
+
export function esc(value) {
|
|
14
|
+
return value
|
|
15
|
+
.replaceAll('&', '&')
|
|
16
|
+
.replaceAll('<', '<')
|
|
17
|
+
.replaceAll('>', '>')
|
|
18
|
+
.replaceAll('"', '"');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=escape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../src/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,GAAG,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
|