ultra11y 2.31.3 → 2.32.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/README.md +2 -1
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1126 -195
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1126 -195
- package/skills/ultra11y/SKILL.md +17 -10
- package/skills/ultra11y/references/audit.md +1 -1
- package/skills/ultra11y/references/methodology.md +1 -1
- package/skills/ultra11y/references/orchestrators.md +184 -0
- package/skills/ultra11y/references/prd.md +9 -36
- package/skills/ultra11y/references/tickets.md +156 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +1126 -195
- package/src/adjudicate.ts +84 -2
- package/src/cli.ts +323 -43
- package/src/config.ts +10 -0
- package/src/gh-cli.ts +39 -0
- package/src/mcp/handlers.ts +28 -0
- package/src/mcp/tools.ts +27 -0
- package/src/pages.ts +13 -0
- package/src/pr-comment.ts +78 -0
- package/src/tickets/grain.ts +218 -0
- package/src/tickets/http.ts +107 -0
- package/src/tickets/providers/github.ts +155 -0
- package/src/tickets/providers/gitlab.ts +158 -0
- package/src/tickets/providers/jira.ts +152 -0
- package/src/tickets/providers/remote.ts +40 -0
- package/src/tickets/push.ts +94 -0
- package/src/tickets/registry.ts +45 -0
- package/src/tickets/render.ts +157 -0
- package/src/tickets/types.ts +154 -0
- package/src/types.ts +1 -1
- package/src/gh.ts +0 -286
package/scripts/ultra11y.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join45, relative as relative5, sep as sep7, dirname as dirname1
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "2.
|
|
9
|
+
var VERSION = "2.32.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -49270,6 +49270,12 @@ var L3 = {
|
|
|
49270
49270
|
source: "source"
|
|
49271
49271
|
}
|
|
49272
49272
|
};
|
|
49273
|
+
function pageBasisWarning(basis, lang) {
|
|
49274
|
+
return basis === "snapshot" ? void 0 : L3[lang].basisNote;
|
|
49275
|
+
}
|
|
49276
|
+
function unattributedNote(n, lang) {
|
|
49277
|
+
return L3[lang].unattributed(n);
|
|
49278
|
+
}
|
|
49273
49279
|
function pageView(result, page) {
|
|
49274
49280
|
return {
|
|
49275
49281
|
...result,
|
|
@@ -49637,22 +49643,9 @@ function writeReport(r, opts) {
|
|
|
49637
49643
|
return path;
|
|
49638
49644
|
}
|
|
49639
49645
|
|
|
49640
|
-
// src/gh.ts
|
|
49646
|
+
// src/gh-cli.ts
|
|
49641
49647
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
49642
|
-
|
|
49643
|
-
var SEV_RANK2 = { bloquant: 0, majeur: 1, mineur: 2 };
|
|
49644
|
-
var ICON4 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
49645
|
-
var SEV_LABEL2 = {
|
|
49646
|
-
fr: { bloquant: "Bloquant", majeur: "Majeur", mineur: "Mineur" },
|
|
49647
|
-
en: { bloquant: "Blocking", majeur: "Major", mineur: "Minor" }
|
|
49648
|
-
};
|
|
49649
|
-
function standardTag(standard) {
|
|
49650
|
-
return isCore(standard) ? { label: "WCAG", tag: "wcag" } : (() => {
|
|
49651
|
-
const p = loadPack(standard);
|
|
49652
|
-
return { label: p.name, tag: p.key };
|
|
49653
|
-
})();
|
|
49654
|
-
}
|
|
49655
|
-
function gh(args2, input) {
|
|
49648
|
+
function ghExec(args2, input) {
|
|
49656
49649
|
return execFileSync3("gh", args2, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], ...input !== void 0 ? { input } : {} });
|
|
49657
49650
|
}
|
|
49658
49651
|
function ghErrorReason(err2) {
|
|
@@ -49672,22 +49665,115 @@ function ghAvailable() {
|
|
|
49672
49665
|
return false;
|
|
49673
49666
|
}
|
|
49674
49667
|
}
|
|
49675
|
-
|
|
49676
|
-
|
|
49677
|
-
|
|
49668
|
+
|
|
49669
|
+
// src/pr-comment.ts
|
|
49670
|
+
function COMMENT_MARKER(standard) {
|
|
49671
|
+
return `<!-- ultra11y:report standard="${standard}" -->`;
|
|
49672
|
+
}
|
|
49673
|
+
function stickyBody(markdown, standard) {
|
|
49674
|
+
return `${COMMENT_MARKER(standard)}
|
|
49675
|
+
${markdown}`;
|
|
49678
49676
|
}
|
|
49679
|
-
function
|
|
49677
|
+
function prNumberFromEnv(env = process.env) {
|
|
49678
|
+
const explicit = Number.parseInt(env.ULTRA11Y_PR ?? "", 10);
|
|
49679
|
+
if (Number.isFinite(explicit) && explicit > 0) return explicit;
|
|
49680
|
+
const m = /^refs\/pull\/(\d+)\//.exec(env.GITHUB_REF ?? "");
|
|
49681
|
+
const n = m ? Number.parseInt(m[1], 10) : Number.NaN;
|
|
49682
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
49683
|
+
}
|
|
49684
|
+
function pickExistingComment(comments, marker) {
|
|
49685
|
+
return comments.find((c2) => typeof c2.body === "string" && c2.body.includes(marker));
|
|
49686
|
+
}
|
|
49687
|
+
function pushPrComment(markdown, standard = "wcag") {
|
|
49688
|
+
const pr = prNumberFromEnv();
|
|
49689
|
+
if (pr === void 0) return { ok: true, action: "skipped", reason: "not a pull-request run" };
|
|
49690
|
+
const marker = COMMENT_MARKER(standard);
|
|
49691
|
+
const body2 = stickyBody(markdown, standard);
|
|
49680
49692
|
try {
|
|
49681
|
-
const
|
|
49682
|
-
|
|
49683
|
-
|
|
49684
|
-
|
|
49685
|
-
|
|
49693
|
+
const repo = ghExec(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]).trim();
|
|
49694
|
+
let existing;
|
|
49695
|
+
try {
|
|
49696
|
+
const raw = ghExec(["api", `repos/${repo}/issues/${pr}/comments`, "--paginate"]);
|
|
49697
|
+
existing = pickExistingComment(JSON.parse(raw), marker);
|
|
49698
|
+
} catch {
|
|
49699
|
+
}
|
|
49700
|
+
if (existing) {
|
|
49701
|
+
ghExec(["api", "--method", "PATCH", `repos/${repo}/issues/comments/${existing.id}`, "-f", `body=${body2}`]);
|
|
49702
|
+
return { ok: true, action: "updated" };
|
|
49703
|
+
}
|
|
49704
|
+
ghExec(["api", "--method", "POST", `repos/${repo}/issues/${pr}/comments`, "-f", `body=${body2}`]);
|
|
49705
|
+
return { ok: true, action: "created" };
|
|
49706
|
+
} catch (e) {
|
|
49707
|
+
return { ok: false, action: "skipped", reason: ghErrorReason(e) };
|
|
49686
49708
|
}
|
|
49687
49709
|
}
|
|
49688
|
-
|
|
49689
|
-
|
|
49690
|
-
|
|
49710
|
+
|
|
49711
|
+
// src/tickets/types.ts
|
|
49712
|
+
var ALL_PROVIDERS = ["github", "gitlab", "jira"];
|
|
49713
|
+
var ALL_GRAINS = ["criterion", "page", "page-criterion", "single", "file"];
|
|
49714
|
+
var UNATTRIBUTED_ID = "unattributed";
|
|
49715
|
+
var TICKET_SET_SCHEMA_VERSION = 1;
|
|
49716
|
+
|
|
49717
|
+
// src/tickets/render.ts
|
|
49718
|
+
var RECOMMENDATION_SUFFIX = " (recommendation)";
|
|
49719
|
+
var AUDIT_FRAME = "Accessibility audit";
|
|
49720
|
+
function standardTag(standard) {
|
|
49721
|
+
return isCore(standard) ? { label: "WCAG", tag: "wcag" } : (() => {
|
|
49722
|
+
const p = loadPack(standard);
|
|
49723
|
+
return { label: p.name, tag: p.key };
|
|
49724
|
+
})();
|
|
49725
|
+
}
|
|
49726
|
+
function criterionTitle(unit, label = "WCAG") {
|
|
49727
|
+
return `[a11y] ${label} ${unit.criteriaId} \u2014 ${unit.title}${unit.advisory ? RECOMMENDATION_SUFFIX : ""}`;
|
|
49728
|
+
}
|
|
49729
|
+
function singleTitle(label = "WCAG") {
|
|
49730
|
+
return `[a11y] ${label} \u2014 ${AUDIT_FRAME}`;
|
|
49731
|
+
}
|
|
49732
|
+
function pageTitle(pageId, label = "WCAG") {
|
|
49733
|
+
return `[a11y] ${label} [page:${pageId}] \u2014 ${AUDIT_FRAME}`;
|
|
49734
|
+
}
|
|
49735
|
+
function pageCriterionTitle(pageId, unit, label = "WCAG") {
|
|
49736
|
+
return `[a11y] ${label} [page:${pageId}] ${unit.criteriaId} \u2014 ${unit.title}${unit.advisory ? RECOMMENDATION_SUFFIX : ""}`;
|
|
49737
|
+
}
|
|
49738
|
+
function fileTitle(file, label = "WCAG") {
|
|
49739
|
+
return `[a11y] ${label} [file:${file}] \u2014 ${AUDIT_FRAME}`;
|
|
49740
|
+
}
|
|
49741
|
+
function unattributedTitle(label = "WCAG") {
|
|
49742
|
+
return `[a11y] ${label} [${UNATTRIBUTED_ID}] \u2014 ${AUDIT_FRAME}`;
|
|
49743
|
+
}
|
|
49744
|
+
function labelsFor(severity, advisory, tag) {
|
|
49745
|
+
return advisory ? ["accessibility", tag, "recommendation", severity] : ["accessibility", tag, severity];
|
|
49746
|
+
}
|
|
49747
|
+
var L5 = {
|
|
49748
|
+
fr: {
|
|
49749
|
+
fix: "Correction",
|
|
49750
|
+
occ: "Occurrence(s)",
|
|
49751
|
+
def: "\u21B3 d\xE9finition",
|
|
49752
|
+
page: "Page",
|
|
49753
|
+
url: "URL",
|
|
49754
|
+
auth: "Authentification requise",
|
|
49755
|
+
yes: "oui",
|
|
49756
|
+
file: "Fichier",
|
|
49757
|
+
fileNote: "Ticket regroup\xE9 par fichier source. Un fichier n'est pas une page : il ne porte aucun taux de conformit\xE9 \u2014 seul le p\xE9rim\xE8tre global et la grille par page en portent un.",
|
|
49758
|
+
truncated: (path) => `_\u2026 corps tronqu\xE9 : le ticket d\xE9passait la limite du tracker. Le d\xE9tail complet est dans \`${path}\`._`,
|
|
49759
|
+
prdPath: "audits/prd-<date>.md"
|
|
49760
|
+
},
|
|
49761
|
+
en: {
|
|
49762
|
+
fix: "Fix",
|
|
49763
|
+
occ: "Occurrence(s)",
|
|
49764
|
+
def: "\u21B3 definition",
|
|
49765
|
+
page: "Page",
|
|
49766
|
+
url: "URL",
|
|
49767
|
+
auth: "Authentication required",
|
|
49768
|
+
yes: "yes",
|
|
49769
|
+
file: "File",
|
|
49770
|
+
fileNote: "Ticket grouped by source file. A file is not a page: it carries no conformance rate \u2014 only the overall scope and the per-page grid do.",
|
|
49771
|
+
truncated: (path) => `_\u2026 body truncated: the ticket exceeded the tracker's limit. The full detail is in \`${path}\`._`,
|
|
49772
|
+
prdPath: "audits/prd-<date>.md"
|
|
49773
|
+
}
|
|
49774
|
+
};
|
|
49775
|
+
function renderRemediationBody(unit, lang) {
|
|
49776
|
+
const t2 = L5[lang];
|
|
49691
49777
|
const lines = [];
|
|
49692
49778
|
if (unit.refs.length) lines.push(`**WCAG** : ${unit.refs.join(", ")}`, "");
|
|
49693
49779
|
for (const fx of [...new Set(unit.findings.map((f) => resolveRemediation(f, lang)))]) lines.push(`**${t2.fix}** : ${fx}`);
|
|
@@ -49698,122 +49784,662 @@ function issueBody(unit, lang, standard = "wcag", format = "audit") {
|
|
|
49698
49784
|
}
|
|
49699
49785
|
return lines.join("\n");
|
|
49700
49786
|
}
|
|
49701
|
-
function
|
|
49702
|
-
|
|
49703
|
-
|
|
49704
|
-
|
|
49705
|
-
|
|
49706
|
-
|
|
49707
|
-
|
|
49708
|
-
|
|
49709
|
-
|
|
49710
|
-
|
|
49711
|
-
|
|
49787
|
+
function renderCriterionBody(unit, standard, lang, opts = {}) {
|
|
49788
|
+
if (opts.format === "remediation") return renderRemediationBody(unit, lang);
|
|
49789
|
+
return renderAuditorUnit(unit, standard, lang, { ...opts.technical !== void 0 ? { technical: opts.technical } : {} }).join("\n").trimEnd();
|
|
49790
|
+
}
|
|
49791
|
+
function pagePreamble(page, lang, basisWarning) {
|
|
49792
|
+
const t2 = L5[lang];
|
|
49793
|
+
const out2 = [`**${t2.page}** : ${page.name}`, `**${t2.url}** : ${page.url}`];
|
|
49794
|
+
if (page.auth) out2.push(`**${t2.auth}** : ${t2.yes}`);
|
|
49795
|
+
out2.push("");
|
|
49796
|
+
if (basisWarning) out2.push(`> \u26A0\uFE0F ${basisWarning}`, "");
|
|
49797
|
+
return out2;
|
|
49798
|
+
}
|
|
49799
|
+
function filePreamble(file, lang) {
|
|
49800
|
+
const t2 = L5[lang];
|
|
49801
|
+
return [`**${t2.file}** : \`${file}\``, "", `> ${t2.fileNote}`, ""];
|
|
49802
|
+
}
|
|
49803
|
+
function clampBody(body2, limit, lang) {
|
|
49804
|
+
if (body2.length <= limit) return body2;
|
|
49805
|
+
const notice = `
|
|
49806
|
+
|
|
49807
|
+
${L5[lang].truncated(L5[lang].prdPath)}`;
|
|
49808
|
+
const budget = limit - notice.length;
|
|
49809
|
+
const head = body2.slice(0, Math.max(0, budget));
|
|
49810
|
+
const cut = head.lastIndexOf("\n## ");
|
|
49811
|
+
return `${(cut > 0 ? head.slice(0, cut) : head).trimEnd()}${notice}`;
|
|
49812
|
+
}
|
|
49813
|
+
|
|
49814
|
+
// src/tickets/grain.ts
|
|
49815
|
+
var SEV_RANK2 = { bloquant: 0, majeur: 1, mineur: 2 };
|
|
49816
|
+
function worstOf(items) {
|
|
49817
|
+
return items.reduce((worst, x) => SEV_RANK2[x.severity] < SEV_RANK2[worst] ? x.severity : worst, "mineur");
|
|
49818
|
+
}
|
|
49819
|
+
function fileView(result, file, baseDir) {
|
|
49820
|
+
const owns = (f) => sourceOf(f, baseDir) === file;
|
|
49821
|
+
return {
|
|
49822
|
+
...result,
|
|
49823
|
+
findings: result.findings.filter(owns),
|
|
49824
|
+
...result.packFindings ? { packFindings: result.packFindings.filter(owns) } : {}
|
|
49825
|
+
};
|
|
49826
|
+
}
|
|
49827
|
+
function occurrencesOf(units, lang) {
|
|
49828
|
+
return units.flatMap((u) => u.findings.map((f) => ({ file: f.file, line: f.line, selector: f.selectorHint, message: resolveMessage(f, lang) })));
|
|
49829
|
+
}
|
|
49830
|
+
function sourceOf(f, baseDir) {
|
|
49831
|
+
return repoRelative(f.origin?.sourceFile ?? f.file, baseDir);
|
|
49832
|
+
}
|
|
49833
|
+
function buildTickets(result, opts) {
|
|
49834
|
+
const { grain, standard, lang } = opts;
|
|
49835
|
+
const { label, tag } = standardTag(standard);
|
|
49836
|
+
const limit = opts.bodyLimit ?? Number.POSITIVE_INFINITY;
|
|
49837
|
+
const bodyOpts = {
|
|
49838
|
+
...opts.format !== void 0 ? { format: opts.format } : {},
|
|
49839
|
+
...opts.technical !== void 0 ? { technical: opts.technical } : {}
|
|
49840
|
+
};
|
|
49841
|
+
const backlogOpts = opts.technical !== void 0 ? { technical: opts.technical } : {};
|
|
49842
|
+
const baseDir = opts.baseDir ?? "";
|
|
49843
|
+
const clamp2 = (body2) => clampBody(body2, limit, lang);
|
|
49844
|
+
const ticketFromUnit = (unit, title2, scope2) => ({
|
|
49845
|
+
title: title2,
|
|
49846
|
+
body: clamp2(renderCriterionBody(unit, standard, lang, bodyOpts)),
|
|
49847
|
+
labels: labelsFor(unit.severity, unit.advisory === true, tag),
|
|
49848
|
+
severity: unit.severity,
|
|
49849
|
+
advisory: unit.advisory === true,
|
|
49850
|
+
scope: scope2,
|
|
49851
|
+
occurrences: occurrencesOf([unit], lang)
|
|
49852
|
+
});
|
|
49853
|
+
if (grain === "criterion") {
|
|
49854
|
+
const tickets2 = prdUnits(result, standard, lang).map((u) => ticketFromUnit(u, criterionTitle(u, label), { grain: "criterion", criteriaId: u.criteriaId }));
|
|
49855
|
+
return { tickets: tickets2, unattributed: 0 };
|
|
49856
|
+
}
|
|
49857
|
+
if (grain === "single") {
|
|
49858
|
+
const units = prdUnits(result, standard, lang);
|
|
49859
|
+
if (!units.length) return { tickets: [], unattributed: 0 };
|
|
49860
|
+
return {
|
|
49861
|
+
tickets: [
|
|
49862
|
+
{
|
|
49863
|
+
title: singleTitle(label),
|
|
49864
|
+
// renderAuditorBacklog — not a second template. It also fixes a real inconsistency
|
|
49865
|
+
// in the pre-v3 consolidated body, which sectioned advisory units AMONG the
|
|
49866
|
+
// severity groups instead of isolating them in their own trailing section.
|
|
49867
|
+
body: clamp2(renderAuditorBacklog(result, lang, standard, backlogOpts)),
|
|
49868
|
+
labels: labelsFor(
|
|
49869
|
+
worstOf(units),
|
|
49870
|
+
units.every((u) => u.advisory === true),
|
|
49871
|
+
tag
|
|
49872
|
+
),
|
|
49873
|
+
severity: worstOf(units),
|
|
49874
|
+
advisory: units.every((u) => u.advisory === true),
|
|
49875
|
+
scope: { grain: "single" },
|
|
49876
|
+
occurrences: occurrencesOf(units, lang)
|
|
49877
|
+
}
|
|
49878
|
+
],
|
|
49879
|
+
unattributed: 0
|
|
49880
|
+
};
|
|
49881
|
+
}
|
|
49882
|
+
if (grain === "file") {
|
|
49883
|
+
const files = [...new Set(result.findings.map((f) => sourceOf(f, baseDir)))].sort();
|
|
49884
|
+
const tickets2 = [];
|
|
49885
|
+
for (const file of files) {
|
|
49886
|
+
const view = fileView(result, file, baseDir);
|
|
49887
|
+
const units = prdUnits(view, standard, lang);
|
|
49888
|
+
if (!units.length) continue;
|
|
49889
|
+
const advisory = units.every((u) => u.advisory === true);
|
|
49890
|
+
tickets2.push({
|
|
49891
|
+
title: fileTitle(file, label),
|
|
49892
|
+
body: clamp2([...filePreamble(file, lang), renderAuditorBacklog(view, lang, standard, backlogOpts)].join("\n")),
|
|
49893
|
+
labels: labelsFor(worstOf(units), advisory, tag),
|
|
49894
|
+
severity: worstOf(units),
|
|
49895
|
+
advisory,
|
|
49896
|
+
scope: { grain: "file", file },
|
|
49897
|
+
occurrences: occurrencesOf(units, lang)
|
|
49898
|
+
});
|
|
49899
|
+
}
|
|
49900
|
+
return { tickets: tickets2, unattributed: 0 };
|
|
49901
|
+
}
|
|
49902
|
+
const scope = pagesOf(result);
|
|
49903
|
+
if (!scope.length) return { tickets: [], unattributed: 0, error: "no-pages" };
|
|
49904
|
+
attributePages(result, scope);
|
|
49905
|
+
const derived = derivePages(result, scope);
|
|
49906
|
+
const orphans = unattributedFindings(result);
|
|
49907
|
+
const tickets = [];
|
|
49908
|
+
for (const page of derived) {
|
|
49909
|
+
const view = pageView(result, page);
|
|
49910
|
+
const units = prdUnits(view, standard, lang);
|
|
49911
|
+
if (!units.length) continue;
|
|
49912
|
+
const warning = pageBasisWarning(page.basis, lang);
|
|
49913
|
+
const preamble = pagePreamble(page, lang, warning);
|
|
49914
|
+
if (grain === "page") {
|
|
49915
|
+
const advisory = units.every((u) => u.advisory === true);
|
|
49916
|
+
tickets.push({
|
|
49917
|
+
title: pageTitle(page.id, label),
|
|
49918
|
+
body: clamp2([...preamble, renderAuditorBacklog(view, lang, standard, backlogOpts)].join("\n")),
|
|
49919
|
+
labels: labelsFor(worstOf(units), advisory, tag),
|
|
49920
|
+
severity: worstOf(units),
|
|
49921
|
+
advisory,
|
|
49922
|
+
scope: pageScopeOf(page),
|
|
49923
|
+
occurrences: occurrencesOf(units, lang)
|
|
49924
|
+
});
|
|
49925
|
+
continue;
|
|
49926
|
+
}
|
|
49927
|
+
for (const u of units) {
|
|
49928
|
+
const t2 = ticketFromUnit(u, pageCriterionTitle(page.id, u, label), {
|
|
49929
|
+
grain: "page-criterion",
|
|
49930
|
+
pageId: page.id,
|
|
49931
|
+
pageName: page.name,
|
|
49932
|
+
url: page.url,
|
|
49933
|
+
...page.auth !== void 0 ? { auth: page.auth } : {},
|
|
49934
|
+
basis: page.basis,
|
|
49935
|
+
criteriaId: u.criteriaId
|
|
49936
|
+
});
|
|
49937
|
+
tickets.push({ ...t2, body: clamp2([...preamble, t2.body].join("\n")) });
|
|
49938
|
+
}
|
|
49939
|
+
}
|
|
49940
|
+
if (orphans.length) {
|
|
49941
|
+
const view = { ...result, findings: orphans, ...result.packFindings ? { packFindings: result.packFindings.filter((f) => !f.page) } : {} };
|
|
49942
|
+
const units = prdUnits(view, standard, lang);
|
|
49943
|
+
if (units.length) {
|
|
49944
|
+
const advisory = units.every((u) => u.advisory === true);
|
|
49945
|
+
tickets.push({
|
|
49946
|
+
title: unattributedTitle(label),
|
|
49947
|
+
body: clamp2([`> ${unattributedNote(orphans.length, lang)}`, "", renderAuditorBacklog(view, lang, standard, backlogOpts)].join("\n")),
|
|
49948
|
+
labels: labelsFor(worstOf(units), advisory, tag),
|
|
49949
|
+
severity: worstOf(units),
|
|
49950
|
+
advisory,
|
|
49951
|
+
scope: { grain: "page", pageId: UNATTRIBUTED_ID, pageName: UNATTRIBUTED_ID, url: "", basis: "none" },
|
|
49952
|
+
occurrences: occurrencesOf(units, lang)
|
|
49953
|
+
});
|
|
49712
49954
|
}
|
|
49713
49955
|
}
|
|
49956
|
+
return { tickets, unattributed: orphans.length };
|
|
49957
|
+
}
|
|
49958
|
+
function pageScopeOf(page) {
|
|
49959
|
+
return {
|
|
49960
|
+
grain: "page",
|
|
49961
|
+
pageId: page.id,
|
|
49962
|
+
pageName: page.name,
|
|
49963
|
+
url: page.url,
|
|
49964
|
+
...page.auth !== void 0 ? { auth: page.auth } : {},
|
|
49965
|
+
basis: page.basis
|
|
49966
|
+
};
|
|
49967
|
+
}
|
|
49968
|
+
|
|
49969
|
+
// src/tickets/push.ts
|
|
49970
|
+
function planPush(tickets, existing) {
|
|
49971
|
+
const seen = new Set(existing.map((e) => e.title));
|
|
49972
|
+
return tickets.map((ticket) => {
|
|
49973
|
+
if (seen.has(ticket.title)) return { ticket, action: "skip" };
|
|
49974
|
+
seen.add(ticket.title);
|
|
49975
|
+
return { ticket, action: "create" };
|
|
49976
|
+
});
|
|
49977
|
+
}
|
|
49978
|
+
function emptyResult() {
|
|
49979
|
+
return { created: 0, skipped: 0, failed: 0, createdTitles: [], createdUrls: [], errors: [] };
|
|
49714
49980
|
}
|
|
49715
49981
|
function recordFailure(result, reason) {
|
|
49716
49982
|
result.failed++;
|
|
49717
49983
|
if (reason && !result.errors.includes(reason)) result.errors.push(reason);
|
|
49718
49984
|
}
|
|
49719
|
-
function
|
|
49720
|
-
const
|
|
49721
|
-
|
|
49722
|
-
|
|
49723
|
-
|
|
49724
|
-
|
|
49725
|
-
|
|
49985
|
+
async function pushTickets(tickets, provider, opts = {}) {
|
|
49986
|
+
const result = emptyResult();
|
|
49987
|
+
if (!tickets.length) return { plan: [], result, dedupeChecked: false };
|
|
49988
|
+
let existing = [];
|
|
49989
|
+
let dedupeChecked = false;
|
|
49990
|
+
try {
|
|
49991
|
+
existing = await provider.list();
|
|
49992
|
+
dedupeChecked = true;
|
|
49993
|
+
} catch {
|
|
49994
|
+
dedupeChecked = false;
|
|
49995
|
+
}
|
|
49996
|
+
const plan = planPush(tickets, existing);
|
|
49997
|
+
if (opts.dryRun) {
|
|
49998
|
+
result.skipped = plan.filter((p) => p.action === "skip").length;
|
|
49999
|
+
return { plan, result, dedupeChecked };
|
|
50000
|
+
}
|
|
50001
|
+
for (const { ticket, action } of plan) {
|
|
50002
|
+
if (action === "skip") {
|
|
49726
50003
|
result.skipped++;
|
|
49727
50004
|
continue;
|
|
49728
50005
|
}
|
|
49729
|
-
|
|
49730
|
-
|
|
49731
|
-
|
|
50006
|
+
let outcome2;
|
|
50007
|
+
try {
|
|
50008
|
+
outcome2 = await provider.create(ticket);
|
|
50009
|
+
} catch (e) {
|
|
50010
|
+
outcome2 = { ok: false, reason: e instanceof Error ? e.message.split("\n")[0] : String(e) };
|
|
50011
|
+
}
|
|
50012
|
+
if (outcome2.ok) {
|
|
49732
50013
|
result.created++;
|
|
49733
|
-
result.createdTitles.push(
|
|
49734
|
-
|
|
50014
|
+
result.createdTitles.push(ticket.title);
|
|
50015
|
+
if (outcome2.url) result.createdUrls.push(outcome2.url);
|
|
49735
50016
|
} else {
|
|
49736
|
-
recordFailure(result,
|
|
50017
|
+
recordFailure(result, outcome2.reason);
|
|
49737
50018
|
}
|
|
49738
50019
|
}
|
|
49739
|
-
return result;
|
|
50020
|
+
return { plan, result, dedupeChecked };
|
|
49740
50021
|
}
|
|
49741
|
-
|
|
49742
|
-
|
|
50022
|
+
|
|
50023
|
+
// src/tickets/http.ts
|
|
50024
|
+
var USER_AGENT = `ultra11y/${VERSION}`;
|
|
50025
|
+
var SECRET_HEADERS = ["authorization", "private-token", "x-atlassian-token"];
|
|
50026
|
+
var HttpError = class extends Error {
|
|
50027
|
+
constructor(status, detail, url) {
|
|
50028
|
+
super(`HTTP ${status}: ${detail}`);
|
|
50029
|
+
this.status = status;
|
|
50030
|
+
this.detail = detail;
|
|
50031
|
+
this.url = url;
|
|
50032
|
+
this.name = "HttpError";
|
|
50033
|
+
}
|
|
50034
|
+
status;
|
|
50035
|
+
detail;
|
|
50036
|
+
url;
|
|
50037
|
+
};
|
|
50038
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
50039
|
+
function scrub(text, headers) {
|
|
50040
|
+
let out2 = text;
|
|
50041
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
50042
|
+
if (v && SECRET_HEADERS.includes(k.toLowerCase())) out2 = out2.split(v).join("<redacted>");
|
|
50043
|
+
}
|
|
50044
|
+
return out2;
|
|
49743
50045
|
}
|
|
49744
|
-
function
|
|
49745
|
-
|
|
49746
|
-
|
|
49747
|
-
|
|
49748
|
-
|
|
49749
|
-
|
|
49750
|
-
|
|
49751
|
-
|
|
49752
|
-
|
|
49753
|
-
|
|
49754
|
-
|
|
50046
|
+
function reasonFrom(detail) {
|
|
50047
|
+
try {
|
|
50048
|
+
const j = JSON.parse(detail);
|
|
50049
|
+
if (Array.isArray(j.errorMessages) && typeof j.errorMessages[0] === "string") return j.errorMessages[0];
|
|
50050
|
+
if (j.errors && typeof j.errors === "object" && !Array.isArray(j.errors)) {
|
|
50051
|
+
const first = Object.entries(j.errors)[0];
|
|
50052
|
+
if (first) return `${first[0]}: ${String(first[1])}`;
|
|
50053
|
+
}
|
|
50054
|
+
if (typeof j.message === "string") return j.message;
|
|
50055
|
+
if (typeof j.error === "string") return j.error;
|
|
50056
|
+
} catch {
|
|
50057
|
+
}
|
|
50058
|
+
return detail.split("\n")[0]?.slice(0, 300) ?? detail.slice(0, 300);
|
|
50059
|
+
}
|
|
50060
|
+
async function requestJson(url, opts = {}) {
|
|
50061
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
50062
|
+
const sleep2 = opts.sleep ?? defaultSleep;
|
|
50063
|
+
const retries = opts.retries ?? 2;
|
|
50064
|
+
const headers = {
|
|
50065
|
+
Accept: "application/json",
|
|
50066
|
+
"User-Agent": USER_AGENT,
|
|
50067
|
+
// GitHub REJECTS a request without one.
|
|
50068
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
50069
|
+
...opts.headers
|
|
50070
|
+
};
|
|
50071
|
+
for (let attempt = 0; ; attempt++) {
|
|
50072
|
+
const res = await doFetch(url, {
|
|
50073
|
+
method: opts.method ?? "GET",
|
|
50074
|
+
headers,
|
|
50075
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
|
|
50076
|
+
});
|
|
50077
|
+
if (res.ok) {
|
|
50078
|
+
const text = await res.text();
|
|
50079
|
+
return { data: text ? JSON.parse(text) : {}, headers: res.headers };
|
|
49755
50080
|
}
|
|
50081
|
+
const detail = scrub(await res.text().catch(() => ""), headers);
|
|
50082
|
+
const retryable = res.status === 429 || res.status === 503;
|
|
50083
|
+
if (!retryable || attempt >= retries) throw new HttpError(res.status, detail, url);
|
|
50084
|
+
const after = Number.parseInt(res.headers.get("retry-after") ?? "", 10);
|
|
50085
|
+
await sleep2(Math.min(Number.isFinite(after) ? after * 1e3 : 1e3 * 2 ** attempt, 3e4));
|
|
49756
50086
|
}
|
|
49757
|
-
return lines.join("\n");
|
|
49758
50087
|
}
|
|
49759
|
-
|
|
49760
|
-
|
|
49761
|
-
|
|
49762
|
-
|
|
49763
|
-
|
|
49764
|
-
|
|
49765
|
-
|
|
49766
|
-
|
|
49767
|
-
|
|
49768
|
-
const severity = [...units].sort((a, b) => SEV_RANK2[a.severity] - SEV_RANK2[b.severity])[0].severity;
|
|
49769
|
-
const r = createIssue(title2, singleIssueBody(units, lang, standard, format), ["accessibility", tag, severity]);
|
|
49770
|
-
if (r.ok) {
|
|
49771
|
-
result.created = 1;
|
|
49772
|
-
result.createdTitles.push(title2);
|
|
49773
|
-
} else {
|
|
49774
|
-
recordFailure(result, r.reason);
|
|
50088
|
+
|
|
50089
|
+
// src/tickets/providers/remote.ts
|
|
50090
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
50091
|
+
function originUrl() {
|
|
50092
|
+
try {
|
|
50093
|
+
const url = execFileSync4("git", ["config", "--get", "remote.origin.url"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
50094
|
+
return url || void 0;
|
|
50095
|
+
} catch {
|
|
50096
|
+
return void 0;
|
|
49775
50097
|
}
|
|
49776
|
-
return result;
|
|
49777
50098
|
}
|
|
49778
|
-
function
|
|
49779
|
-
|
|
50099
|
+
function gitRemoteSlug(host) {
|
|
50100
|
+
const url = originUrl();
|
|
50101
|
+
if (!url) return void 0;
|
|
50102
|
+
const m = /^(?:git@|ssh:\/\/git@|https?:\/\/)(?:[^@/]*@)?([^:/]+)[:/](.+?)(?:\.git)?\/?$/.exec(url);
|
|
50103
|
+
if (!m) return void 0;
|
|
50104
|
+
const [, remoteHost, path] = m;
|
|
50105
|
+
if (!remoteHost || !path) return void 0;
|
|
50106
|
+
const matches = host.endsWith(".") ? remoteHost.startsWith(host) : remoteHost === host || remoteHost.endsWith(`.${host}`);
|
|
50107
|
+
return matches ? path : void 0;
|
|
50108
|
+
}
|
|
50109
|
+
function providerFromRemote() {
|
|
50110
|
+
const url = originUrl();
|
|
50111
|
+
if (!url) return void 0;
|
|
50112
|
+
if (/github\.com|github\./i.test(url)) return "github";
|
|
50113
|
+
if (/gitlab\.com|gitlab\./i.test(url)) return "gitlab";
|
|
50114
|
+
return void 0;
|
|
49780
50115
|
}
|
|
49781
|
-
|
|
49782
|
-
|
|
49783
|
-
|
|
50116
|
+
|
|
50117
|
+
// src/tickets/providers/github.ts
|
|
50118
|
+
var CAPABILITIES = { bodyLimit: 65536, labels: true };
|
|
50119
|
+
function resolve4(opts) {
|
|
50120
|
+
const env = opts.env ?? process.env;
|
|
50121
|
+
const mode = opts.transport ?? "auto";
|
|
50122
|
+
const api = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
|
|
50123
|
+
const token = env.GH_TOKEN || env.GITHUB_TOKEN;
|
|
50124
|
+
const cliOk = (opts.cliAvailable ?? ghAvailable)();
|
|
50125
|
+
if (mode === "cli") {
|
|
50126
|
+
return cliOk ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
|
|
50127
|
+
}
|
|
50128
|
+
const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk ? gitRemoteSlug("github.com") : void 0);
|
|
50129
|
+
if (mode === "rest") {
|
|
50130
|
+
if (!token) return { transport: "rest", api, reason: "no GitHub token \u2014 set GH_TOKEN or GITHUB_TOKEN" };
|
|
50131
|
+
if (!repo) return { transport: "rest", api, token, reason: "no repository \u2014 set GITHUB_REPOSITORY (owner/name) or ULTRA11Y_GITHUB_REPO" };
|
|
50132
|
+
return { transport: "rest", api, token, repo };
|
|
50133
|
+
}
|
|
50134
|
+
if (cliOk) return { transport: "cli", api };
|
|
50135
|
+
if (token && repo) return { transport: "rest", api, token, repo };
|
|
50136
|
+
const missing = [!token ? "a token (GH_TOKEN/GITHUB_TOKEN)" : "", !repo ? "a repository (GITHUB_REPOSITORY)" : ""].filter(Boolean);
|
|
50137
|
+
return {
|
|
50138
|
+
transport: "rest",
|
|
50139
|
+
api,
|
|
50140
|
+
...token ? { token } : {},
|
|
50141
|
+
...repo ? { repo } : {},
|
|
50142
|
+
reason: `\`gh\` is unavailable and REST is missing ${missing.join(" and ")}`
|
|
50143
|
+
};
|
|
49784
50144
|
}
|
|
49785
|
-
function
|
|
49786
|
-
const
|
|
49787
|
-
|
|
49788
|
-
const
|
|
49789
|
-
|
|
49790
|
-
|
|
50145
|
+
function createGithubProvider(opts = {}) {
|
|
50146
|
+
const r = resolve4(opts);
|
|
50147
|
+
const auth = { Authorization: `Bearer ${r.token ?? ""}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
|
|
50148
|
+
const http = {
|
|
50149
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
|
|
50150
|
+
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
50151
|
+
};
|
|
50152
|
+
async function listRest() {
|
|
50153
|
+
const out2 = [];
|
|
50154
|
+
for (let page = 1; page <= 10; page++) {
|
|
50155
|
+
const url = `${r.api}/repos/${r.repo}/issues?state=all&per_page=100&page=${page}`;
|
|
50156
|
+
const { data: data2 } = await requestJson(url, {
|
|
50157
|
+
headers: auth,
|
|
50158
|
+
...http
|
|
50159
|
+
});
|
|
50160
|
+
if (!Array.isArray(data2) || data2.length === 0) break;
|
|
50161
|
+
for (const i2 of data2) {
|
|
50162
|
+
if (i2.pull_request) continue;
|
|
50163
|
+
if (i2.title) out2.push({ title: i2.title, ...i2.number !== void 0 ? { id: String(i2.number) } : {}, ...i2.html_url ? { url: i2.html_url } : {} });
|
|
50164
|
+
}
|
|
50165
|
+
if (data2.length < 100) break;
|
|
50166
|
+
}
|
|
50167
|
+
return out2;
|
|
50168
|
+
}
|
|
50169
|
+
function listCli() {
|
|
50170
|
+
const raw = ghExec(["issue", "list", "--state", "all", "--limit", "1000", "--json", "title"]);
|
|
50171
|
+
return JSON.parse(raw).filter((i2) => i2.title).map((i2) => ({ title: i2.title }));
|
|
50172
|
+
}
|
|
50173
|
+
async function createRest(t2) {
|
|
50174
|
+
const url = `${r.api}/repos/${r.repo}/issues`;
|
|
50175
|
+
const post = async (labels) => requestJson(url, {
|
|
50176
|
+
method: "POST",
|
|
50177
|
+
headers: auth,
|
|
50178
|
+
body: { title: t2.title, body: t2.body, ...labels ? { labels } : {} },
|
|
50179
|
+
...http
|
|
50180
|
+
});
|
|
50181
|
+
try {
|
|
50182
|
+
const { data: data2 } = await post(t2.labels);
|
|
50183
|
+
return { ok: true, ...data2.number !== void 0 ? { id: String(data2.number) } : {}, ...data2.html_url ? { url: data2.html_url } : {} };
|
|
50184
|
+
} catch (e) {
|
|
50185
|
+
if (e instanceof HttpError && e.status === 422) {
|
|
50186
|
+
try {
|
|
50187
|
+
const { data: data2 } = await post();
|
|
50188
|
+
return { ok: true, ...data2.number !== void 0 ? { id: String(data2.number) } : {}, ...data2.html_url ? { url: data2.html_url } : {} };
|
|
50189
|
+
} catch (retryErr) {
|
|
50190
|
+
return { ok: false, reason: retryErr instanceof HttpError ? reasonFrom(retryErr.detail) : String(retryErr) };
|
|
50191
|
+
}
|
|
50192
|
+
}
|
|
50193
|
+
return { ok: false, reason: e instanceof HttpError ? reasonFrom(e.detail) : e instanceof Error ? e.message : String(e) };
|
|
50194
|
+
}
|
|
50195
|
+
}
|
|
50196
|
+
function createCli(t2) {
|
|
50197
|
+
const base = ["issue", "create", "--title", t2.title, "--body-file", "-"];
|
|
50198
|
+
try {
|
|
50199
|
+
ghExec([...base, "--label", t2.labels.join(",")], t2.body);
|
|
50200
|
+
return { ok: true };
|
|
50201
|
+
} catch (labelledErr) {
|
|
50202
|
+
try {
|
|
50203
|
+
ghExec(base, t2.body);
|
|
50204
|
+
return { ok: true };
|
|
50205
|
+
} catch (err2) {
|
|
50206
|
+
return { ok: false, reason: ghErrorReason(err2) ?? ghErrorReason(labelledErr) };
|
|
50207
|
+
}
|
|
50208
|
+
}
|
|
50209
|
+
}
|
|
50210
|
+
return {
|
|
50211
|
+
id: "github",
|
|
50212
|
+
transport: r.transport,
|
|
50213
|
+
capabilities: CAPABILITIES,
|
|
50214
|
+
available: () => r.reason === void 0,
|
|
50215
|
+
unavailableReason: () => r.reason,
|
|
50216
|
+
list: async () => {
|
|
50217
|
+
try {
|
|
50218
|
+
return r.transport === "cli" ? listCli() : await listRest();
|
|
50219
|
+
} catch {
|
|
50220
|
+
return [];
|
|
50221
|
+
}
|
|
50222
|
+
},
|
|
50223
|
+
create: async (t2) => r.transport === "cli" ? createCli(t2) : createRest(t2)
|
|
50224
|
+
};
|
|
49791
50225
|
}
|
|
49792
|
-
|
|
49793
|
-
|
|
50226
|
+
|
|
50227
|
+
// src/tickets/providers/gitlab.ts
|
|
50228
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
50229
|
+
var CAPABILITIES2 = { bodyLimit: 1e6, labels: true };
|
|
50230
|
+
function glabExec(args2) {
|
|
50231
|
+
return execFileSync5("glab", args2, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
49794
50232
|
}
|
|
49795
|
-
function
|
|
49796
|
-
const pr = prNumberFromEnv();
|
|
49797
|
-
if (pr === void 0) return { ok: true, action: "skipped", reason: "not a pull-request run" };
|
|
49798
|
-
const marker = COMMENT_MARKER(standard);
|
|
49799
|
-
const body2 = stickyBody(markdown, standard);
|
|
50233
|
+
function glabAvailable() {
|
|
49800
50234
|
try {
|
|
49801
|
-
|
|
49802
|
-
|
|
50235
|
+
execFileSync5("glab", ["auth", "status"], { stdio: "ignore" });
|
|
50236
|
+
return true;
|
|
50237
|
+
} catch {
|
|
50238
|
+
return false;
|
|
50239
|
+
}
|
|
50240
|
+
}
|
|
50241
|
+
function execReason(err2) {
|
|
50242
|
+
if (!err2 || typeof err2 !== "object") return void 0;
|
|
50243
|
+
const e = err2;
|
|
50244
|
+
const stderr = typeof e.stderr === "string" ? e.stderr : Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf8") : "";
|
|
50245
|
+
const line = stderr.split("\n").map((l) => l.trim()).find(Boolean);
|
|
50246
|
+
if (line) return line;
|
|
50247
|
+
return typeof e.message === "string" ? e.message.split("\n")[0]?.trim() ?? void 0 : void 0;
|
|
50248
|
+
}
|
|
50249
|
+
function resolve5(opts) {
|
|
50250
|
+
const env = opts.env ?? process.env;
|
|
50251
|
+
const mode = opts.transport ?? "auto";
|
|
50252
|
+
const api = (env.CI_API_V4_URL || "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
50253
|
+
const token = env.GITLAB_TOKEN || env.CI_JOB_TOKEN;
|
|
50254
|
+
const jobToken = !env.GITLAB_TOKEN && !!env.CI_JOB_TOKEN;
|
|
50255
|
+
const cliOk = (opts.cliAvailable ?? glabAvailable)();
|
|
50256
|
+
if (mode === "cli") {
|
|
50257
|
+
return cliOk ? { transport: "cli", api, jobToken } : { transport: "cli", api, jobToken, reason: "`glab` is not installed or not authenticated (run `glab auth login`)" };
|
|
50258
|
+
}
|
|
50259
|
+
const project = env.ULTRA11Y_GITLAB_PROJECT || env.CI_PROJECT_ID || gitRemoteSlug("gitlab.");
|
|
50260
|
+
if (mode === "rest" || !cliOk) {
|
|
50261
|
+
const base = { transport: "rest", api, jobToken, ...token ? { token } : {}, ...project ? { project } : {} };
|
|
50262
|
+
if (!token) return { ...base, reason: `${mode === "auto" ? "`glab` is unavailable and REST has " : ""}no GitLab token \u2014 set GITLAB_TOKEN` };
|
|
50263
|
+
if (!project) return { ...base, reason: "no project \u2014 set CI_PROJECT_ID or ULTRA11Y_GITLAB_PROJECT (id or group/project)" };
|
|
50264
|
+
return base;
|
|
50265
|
+
}
|
|
50266
|
+
return { transport: "cli", api, jobToken };
|
|
50267
|
+
}
|
|
50268
|
+
function createGitlabProvider(opts = {}) {
|
|
50269
|
+
const r = resolve5(opts);
|
|
50270
|
+
const id = encodeURIComponent(r.project ?? "");
|
|
50271
|
+
const auth = { "PRIVATE-TOKEN": r.token ?? "" };
|
|
50272
|
+
const http = {
|
|
50273
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
|
|
50274
|
+
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
50275
|
+
};
|
|
50276
|
+
const withTokenHint = (reason) => r.jobToken && /403|forbidden|unauthorized|401/i.test(reason) ? `${reason} \u2014 CI_JOB_TOKEN cannot create issues; use a project access token in GITLAB_TOKEN` : reason;
|
|
50277
|
+
async function listRest() {
|
|
50278
|
+
const out2 = [];
|
|
50279
|
+
for (let page = 1; page <= 10; page++) {
|
|
50280
|
+
const url = `${r.api}/projects/${id}/issues?state=all&per_page=100&page=${page}`;
|
|
50281
|
+
const { data: data2, headers } = await requestJson(url, { headers: auth, ...http });
|
|
50282
|
+
if (!Array.isArray(data2) || data2.length === 0) break;
|
|
50283
|
+
for (const i2 of data2)
|
|
50284
|
+
if (i2.title) out2.push({ title: i2.title, ...i2.iid !== void 0 ? { id: String(i2.iid) } : {}, ...i2.web_url ? { url: i2.web_url } : {} });
|
|
50285
|
+
if (!headers.get("x-next-page")) break;
|
|
50286
|
+
}
|
|
50287
|
+
return out2;
|
|
50288
|
+
}
|
|
50289
|
+
function listCli() {
|
|
50290
|
+
const raw = glabExec(["issue", "list", "--all", "--output", "json"]);
|
|
50291
|
+
const parsed = JSON.parse(raw);
|
|
50292
|
+
const rows = Array.isArray(parsed) ? parsed : parsed.issues ?? [];
|
|
50293
|
+
return rows.filter((i2) => i2.title).map((i2) => ({ title: i2.title }));
|
|
50294
|
+
}
|
|
50295
|
+
async function createRest(t2) {
|
|
49803
50296
|
try {
|
|
49804
|
-
const
|
|
49805
|
-
|
|
49806
|
-
|
|
50297
|
+
const { data: data2 } = await requestJson(`${r.api}/projects/${id}/issues`, {
|
|
50298
|
+
method: "POST",
|
|
50299
|
+
headers: auth,
|
|
50300
|
+
body: { title: t2.title, description: t2.body, labels: t2.labels.join(",") },
|
|
50301
|
+
...http
|
|
50302
|
+
});
|
|
50303
|
+
return { ok: true, ...data2.iid !== void 0 ? { id: String(data2.iid) } : {}, ...data2.web_url ? { url: data2.web_url } : {} };
|
|
50304
|
+
} catch (e) {
|
|
50305
|
+
const reason = e instanceof HttpError ? reasonFrom(e.detail) : e instanceof Error ? e.message : String(e);
|
|
50306
|
+
return { ok: false, reason: withTokenHint(e instanceof HttpError ? `${e.status} ${reason}` : reason) };
|
|
49807
50307
|
}
|
|
49808
|
-
|
|
49809
|
-
|
|
49810
|
-
|
|
50308
|
+
}
|
|
50309
|
+
function createCli(t2) {
|
|
50310
|
+
try {
|
|
50311
|
+
glabExec(["issue", "create", "--title", t2.title, "--description", t2.body, "--label", t2.labels.join(",")]);
|
|
50312
|
+
return { ok: true };
|
|
50313
|
+
} catch (err2) {
|
|
50314
|
+
return { ok: false, reason: withTokenHint(execReason(err2) ?? "glab issue create failed") };
|
|
49811
50315
|
}
|
|
49812
|
-
gh(["api", "--method", "POST", `repos/${repo}/issues/${pr}/comments`, "-f", `body=${body2}`]);
|
|
49813
|
-
return { ok: true, action: "created" };
|
|
49814
|
-
} catch (e) {
|
|
49815
|
-
return { ok: false, action: "skipped", reason: ghErrorReason(e) };
|
|
49816
50316
|
}
|
|
50317
|
+
return {
|
|
50318
|
+
id: "gitlab",
|
|
50319
|
+
transport: r.transport,
|
|
50320
|
+
capabilities: CAPABILITIES2,
|
|
50321
|
+
available: () => r.reason === void 0,
|
|
50322
|
+
unavailableReason: () => r.reason,
|
|
50323
|
+
list: async () => {
|
|
50324
|
+
try {
|
|
50325
|
+
return r.transport === "cli" ? listCli() : await listRest();
|
|
50326
|
+
} catch {
|
|
50327
|
+
return [];
|
|
50328
|
+
}
|
|
50329
|
+
},
|
|
50330
|
+
create: async (t2) => r.transport === "cli" ? createCli(t2) : createRest(t2)
|
|
50331
|
+
};
|
|
50332
|
+
}
|
|
50333
|
+
|
|
50334
|
+
// src/tickets/providers/jira.ts
|
|
50335
|
+
var CAPABILITIES3 = { bodyLimit: 32767, labels: true };
|
|
50336
|
+
var JIRA_SCOPE_LABEL = "ultra11y";
|
|
50337
|
+
var PRIORITY = { bloquant: "Highest", majeur: "High", mineur: "Low" };
|
|
50338
|
+
function resolve6(opts) {
|
|
50339
|
+
const env = opts.env ?? process.env;
|
|
50340
|
+
const issueType = env.ULTRA11Y_JIRA_ISSUE_TYPE || "Task";
|
|
50341
|
+
const apiVersion = env.ULTRA11Y_JIRA_API === "2" ? "2" : "3";
|
|
50342
|
+
const url = (env.ULTRA11Y_JIRA_URL || "").replace(/\/+$/, "");
|
|
50343
|
+
const project = env.ULTRA11Y_JIRA_PROJECT;
|
|
50344
|
+
const base = { issueType, apiVersion, ...url ? { url } : {}, ...project ? { project } : {} };
|
|
50345
|
+
if (opts.transport === "cli") return { ...base, reason: "Jira has no CLI transport \u2014 use --transport rest (the default) with ULTRA11Y_JIRA_URL and a token" };
|
|
50346
|
+
if (!url) return { ...base, reason: "no Jira site \u2014 set ULTRA11Y_JIRA_URL (e.g. https://acme.atlassian.net)" };
|
|
50347
|
+
if (!project) return { ...base, reason: "no Jira project \u2014 set ULTRA11Y_JIRA_PROJECT (the project KEY)" };
|
|
50348
|
+
if (env.JIRA_API_TOKEN && env.JIRA_EMAIL) return { ...base, auth: `Basic ${Buffer.from(`${env.JIRA_EMAIL}:${env.JIRA_API_TOKEN}`).toString("base64")}` };
|
|
50349
|
+
if (env.JIRA_TOKEN) return { ...base, auth: `Bearer ${env.JIRA_TOKEN}` };
|
|
50350
|
+
return { ...base, reason: "no Jira credentials \u2014 set JIRA_EMAIL + JIRA_API_TOKEN (Cloud) or JIRA_TOKEN (Server/DC)" };
|
|
50351
|
+
}
|
|
50352
|
+
function toAdf(markdown) {
|
|
50353
|
+
const content = markdown.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).map((line) => ({ type: "paragraph", content: [{ type: "text", text: line }] }));
|
|
50354
|
+
return { type: "doc", version: 1, content: content.length ? content : [{ type: "paragraph", content: [] }] };
|
|
50355
|
+
}
|
|
50356
|
+
function createJiraProvider(opts = {}) {
|
|
50357
|
+
const r = resolve6(opts);
|
|
50358
|
+
const headers = { Authorization: r.auth ?? "" };
|
|
50359
|
+
const http = {
|
|
50360
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
|
|
50361
|
+
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
50362
|
+
};
|
|
50363
|
+
async function list() {
|
|
50364
|
+
const out2 = [];
|
|
50365
|
+
const jql = `project = "${r.project}" AND labels = "${JIRA_SCOPE_LABEL}" ORDER BY created DESC`;
|
|
50366
|
+
for (let startAt = 0; startAt < 500; startAt += 100) {
|
|
50367
|
+
const url = `${r.url}/rest/api/2/search?jql=${encodeURIComponent(jql)}&fields=summary&maxResults=100&startAt=${startAt}`;
|
|
50368
|
+
const { data: data2 } = await requestJson(url, { headers, ...http });
|
|
50369
|
+
const issues = data2.issues ?? [];
|
|
50370
|
+
if (!issues.length) break;
|
|
50371
|
+
for (const i2 of issues) if (i2.fields?.summary) out2.push({ title: i2.fields.summary, ...i2.key ? { id: i2.key, url: `${r.url}/browse/${i2.key}` } : {} });
|
|
50372
|
+
if (issues.length < 100) break;
|
|
50373
|
+
}
|
|
50374
|
+
return out2;
|
|
50375
|
+
}
|
|
50376
|
+
function fields(t2, opt) {
|
|
50377
|
+
return {
|
|
50378
|
+
project: { key: r.project },
|
|
50379
|
+
summary: t2.title,
|
|
50380
|
+
description: r.apiVersion === "3" ? toAdf(t2.body) : t2.body,
|
|
50381
|
+
issuetype: { name: r.issueType },
|
|
50382
|
+
...opt.labels ? { labels: [.../* @__PURE__ */ new Set([...t2.labels, JIRA_SCOPE_LABEL])].map((l) => l.replace(/\s+/g, "-")) } : {},
|
|
50383
|
+
...opt.priority ? { priority: { name: PRIORITY[t2.severity] } } : {}
|
|
50384
|
+
};
|
|
50385
|
+
}
|
|
50386
|
+
async function post(t2, opt) {
|
|
50387
|
+
return requestJson(`${r.url}/rest/api/${r.apiVersion}/issue`, { method: "POST", headers, body: { fields: fields(t2, opt) }, ...http });
|
|
50388
|
+
}
|
|
50389
|
+
async function create(t2) {
|
|
50390
|
+
const attempts = [
|
|
50391
|
+
{ priority: true, labels: true },
|
|
50392
|
+
{ priority: false, labels: true },
|
|
50393
|
+
{ priority: false, labels: false }
|
|
50394
|
+
];
|
|
50395
|
+
let last = "";
|
|
50396
|
+
for (const opt of attempts) {
|
|
50397
|
+
try {
|
|
50398
|
+
const { data: data2 } = await post(t2, opt);
|
|
50399
|
+
return { ok: true, ...data2.key ? { id: data2.key, url: `${r.url}/browse/${data2.key}` } : {} };
|
|
50400
|
+
} catch (e) {
|
|
50401
|
+
last = e instanceof HttpError ? reasonFrom(e.detail) : e instanceof Error ? e.message : String(e);
|
|
50402
|
+
if (!(e instanceof HttpError) || e.status !== 400) return { ok: false, reason: last };
|
|
50403
|
+
}
|
|
50404
|
+
}
|
|
50405
|
+
return { ok: false, reason: last };
|
|
50406
|
+
}
|
|
50407
|
+
return {
|
|
50408
|
+
id: "jira",
|
|
50409
|
+
transport: "rest",
|
|
50410
|
+
capabilities: CAPABILITIES3,
|
|
50411
|
+
available: () => r.reason === void 0,
|
|
50412
|
+
unavailableReason: () => r.reason,
|
|
50413
|
+
list: async () => {
|
|
50414
|
+
try {
|
|
50415
|
+
return await list();
|
|
50416
|
+
} catch {
|
|
50417
|
+
return [];
|
|
50418
|
+
}
|
|
50419
|
+
},
|
|
50420
|
+
create
|
|
50421
|
+
};
|
|
50422
|
+
}
|
|
50423
|
+
|
|
50424
|
+
// src/tickets/registry.ts
|
|
50425
|
+
function isProviderId(v) {
|
|
50426
|
+
return ALL_PROVIDERS.includes(v);
|
|
50427
|
+
}
|
|
50428
|
+
function createProvider(id, opts = {}) {
|
|
50429
|
+
switch (id) {
|
|
50430
|
+
case "github":
|
|
50431
|
+
return createGithubProvider(opts);
|
|
50432
|
+
case "gitlab":
|
|
50433
|
+
return createGitlabProvider(opts);
|
|
50434
|
+
case "jira":
|
|
50435
|
+
return createJiraProvider(opts);
|
|
50436
|
+
}
|
|
50437
|
+
}
|
|
50438
|
+
function autoProvider(env = process.env, configured) {
|
|
50439
|
+
const fromEnv = env.ULTRA11Y_TICKET_PROVIDER;
|
|
50440
|
+
if (fromEnv && isProviderId(fromEnv)) return fromEnv;
|
|
50441
|
+
if (configured && isProviderId(configured)) return configured;
|
|
50442
|
+
return providerFromRemote();
|
|
49817
50443
|
}
|
|
49818
50444
|
|
|
49819
50445
|
// src/render.ts
|
|
@@ -49845,7 +50471,7 @@ function detectFrameworks(deps, has2) {
|
|
|
49845
50471
|
for (const [pkg, label] of Object.entries(KNOWN_LIBS)) if (dep(pkg)) componentLibraries.push(label);
|
|
49846
50472
|
return { frameworks, componentLibraries };
|
|
49847
50473
|
}
|
|
49848
|
-
var
|
|
50474
|
+
var L6 = {
|
|
49849
50475
|
fr: {
|
|
49850
50476
|
title: "Obtenir du HTML rendu \xE0 auditer (render)",
|
|
49851
50477
|
why: "Auditer les sources JSX d'une biblioth\xE8que de composants donne des faux n\xE9gatifs : il faut auditer le HTML r\xE9ellement produit.",
|
|
@@ -49876,7 +50502,7 @@ var L5 = {
|
|
|
49876
50502
|
}
|
|
49877
50503
|
};
|
|
49878
50504
|
function renderPlan(d, lang = "fr") {
|
|
49879
|
-
const s =
|
|
50505
|
+
const s = L6[lang];
|
|
49880
50506
|
const out2 = [`# ${s.title}`, "", `> ${s.why}`, ""];
|
|
49881
50507
|
if (d.componentLibraries.length) out2.push(`> \u{1F9E9} ${s.libNote(d.componentLibraries.join(", "))}`, "");
|
|
49882
50508
|
out2.push(`## ${s.detected}`, "");
|
|
@@ -50421,7 +51047,7 @@ function writeWorklist(items, outDir, semantic, standard = "wcag", lang = "en")
|
|
|
50421
51047
|
|
|
50422
51048
|
// src/grounding.ts
|
|
50423
51049
|
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
50424
|
-
import { resolve as
|
|
51050
|
+
import { resolve as resolve7 } from "path";
|
|
50425
51051
|
var WINDOW = 10;
|
|
50426
51052
|
var norm2 = (s) => s.replace(/\s+/g, " ").trim();
|
|
50427
51053
|
var isUnresolvable = (file) => !file || file === "-" || file === "<stdin>" || file === "stdin";
|
|
@@ -50438,7 +51064,7 @@ function selectorProbes(selector) {
|
|
|
50438
51064
|
}
|
|
50439
51065
|
function groundFinding(g, opts = {}) {
|
|
50440
51066
|
if (isUnresolvable(g.file)) return { ok: true, moved: false };
|
|
50441
|
-
const path =
|
|
51067
|
+
const path = resolve7(opts.cwd ?? process.cwd(), g.file);
|
|
50442
51068
|
if (!existsSync17(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
|
|
50443
51069
|
let text;
|
|
50444
51070
|
try {
|
|
@@ -50493,7 +51119,7 @@ function isPagesReport(md) {
|
|
|
50493
51119
|
return md.slice(0, 400).includes(`<!-- ${PAGES_REPORT_MARKER}`);
|
|
50494
51120
|
}
|
|
50495
51121
|
var MARK2 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
50496
|
-
var
|
|
51122
|
+
var L7 = {
|
|
50497
51123
|
fr: {
|
|
50498
51124
|
docTitle: "Rapport d'accessibilit\xE9 page par page",
|
|
50499
51125
|
indexTitle: "Rapport page par page \u2014 index",
|
|
@@ -50613,7 +51239,7 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
50613
51239
|
const standard = opts.standard ?? CORE2;
|
|
50614
51240
|
const lang = opts.lang ?? "en";
|
|
50615
51241
|
const h = opts.heading ?? "##";
|
|
50616
|
-
const s =
|
|
51242
|
+
const s = L7[lang];
|
|
50617
51243
|
const out2 = [];
|
|
50618
51244
|
out2.push(`${h} ${page.name}${page.auth ? " \u{1F512}" : ""}`, "");
|
|
50619
51245
|
const meta2 = [];
|
|
@@ -50659,7 +51285,7 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
50659
51285
|
return out2.join("\n");
|
|
50660
51286
|
}
|
|
50661
51287
|
function header2(result, pages, standard, lang, title2) {
|
|
50662
|
-
const s =
|
|
51288
|
+
const s = L7[lang];
|
|
50663
51289
|
return [
|
|
50664
51290
|
pagesReportMarker(standard),
|
|
50665
51291
|
"",
|
|
@@ -50674,7 +51300,7 @@ function header2(result, pages, standard, lang, title2) {
|
|
|
50674
51300
|
function renderPagesIndex(result, pages, opts = {}) {
|
|
50675
51301
|
const standard = opts.standard ?? CORE2;
|
|
50676
51302
|
const lang = opts.lang ?? "en";
|
|
50677
|
-
const s =
|
|
51303
|
+
const s = L7[lang];
|
|
50678
51304
|
const out2 = header2(result, pages, standard, lang, s.indexTitle);
|
|
50679
51305
|
out2.push(`> ${s.indexNote}`, "");
|
|
50680
51306
|
out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rate} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
|
|
@@ -50702,7 +51328,7 @@ function renderPagesDocument(result, pages, opts = {}) {
|
|
|
50702
51328
|
function renderPageDocument(result, page, opts = {}) {
|
|
50703
51329
|
const standard = opts.standard ?? CORE2;
|
|
50704
51330
|
const lang = opts.lang ?? "en";
|
|
50705
|
-
const s =
|
|
51331
|
+
const s = L7[lang];
|
|
50706
51332
|
const out2 = [pagesReportMarker(standard), "", `# ${s.docTitle} \u2014 ${page.name}`, "", `- **${s.date}** : ${result.date}`, ""];
|
|
50707
51333
|
out2.push(renderPageReport(result, page, { ...opts, standard, lang, heading: "##" }));
|
|
50708
51334
|
return out2.join("\n");
|
|
@@ -51629,6 +52255,18 @@ var adjudication_default = {
|
|
|
51629
52255
|
|
|
51630
52256
|
// src/adjudicate.ts
|
|
51631
52257
|
var ADJUDICATE_MAX_EVIDENCE = 30;
|
|
52258
|
+
function adjudicationContract() {
|
|
52259
|
+
return {
|
|
52260
|
+
verdicts: [...VERDICTS],
|
|
52261
|
+
manualReasons: [...MANUAL_REASON_VALUES],
|
|
52262
|
+
requires: {
|
|
52263
|
+
C: "a non-empty justification AND citations[] naming the harvested evidence it cleared (each anchor resolvable and drawn from this criterion's own evidence); a criterion with no harvested evidence cannot be C at all",
|
|
52264
|
+
NA: "a non-empty justification",
|
|
52265
|
+
NC: "at least one groundable finding, each citing a normativeRef that resolves against the active standard",
|
|
52266
|
+
manual: `a reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`
|
|
52267
|
+
}
|
|
52268
|
+
};
|
|
52269
|
+
}
|
|
51632
52270
|
var selectorFor = (el) => {
|
|
51633
52271
|
const id = el.attribs.id ? `#${el.attribs.id}` : "";
|
|
51634
52272
|
const cls = el.attribs.class ? `.${el.attribs.class.trim().split(/\s+/)[0]}` : "";
|
|
@@ -51830,6 +52468,31 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
|
51830
52468
|
}
|
|
51831
52469
|
var NC_SEVERITY_DEFAULT = "majeur";
|
|
51832
52470
|
var MANUAL_REASONS = /* @__PURE__ */ new Set(["needs-rendered-dom", "undecidable"]);
|
|
52471
|
+
var VERDICTS = ["C", "NC", "NA", "manual"];
|
|
52472
|
+
var MANUAL_REASON_VALUES = ["needs-rendered-dom", "undecidable"];
|
|
52473
|
+
function normalizeVerdict2(v) {
|
|
52474
|
+
if (typeof v !== "string") return void 0;
|
|
52475
|
+
const k = v.trim().toLowerCase();
|
|
52476
|
+
return VERDICTS.find((x) => x.toLowerCase() === k);
|
|
52477
|
+
}
|
|
52478
|
+
function normalizeManualReason(r) {
|
|
52479
|
+
if (typeof r !== "string") return void 0;
|
|
52480
|
+
const k = r.trim().toLowerCase();
|
|
52481
|
+
return MANUAL_REASON_VALUES.find((x) => x === k);
|
|
52482
|
+
}
|
|
52483
|
+
function canonicalizeAdjudication(adj) {
|
|
52484
|
+
for (const it of adj.items) {
|
|
52485
|
+
if (it.verdict !== null) {
|
|
52486
|
+
const v = normalizeVerdict2(it.verdict);
|
|
52487
|
+
if (v !== void 0) it.verdict = v;
|
|
52488
|
+
}
|
|
52489
|
+
if (it.reason !== null && it.reason !== void 0) {
|
|
52490
|
+
const r = normalizeManualReason(it.reason);
|
|
52491
|
+
if (r !== void 0) it.reason = r;
|
|
52492
|
+
}
|
|
52493
|
+
}
|
|
52494
|
+
return adj;
|
|
52495
|
+
}
|
|
51833
52496
|
function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
51834
52497
|
const r = (ref ?? "").trim();
|
|
51835
52498
|
if (!r) return false;
|
|
@@ -51852,6 +52515,7 @@ function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
|
51852
52515
|
}
|
|
51853
52516
|
function applyAdjudication(audit2, adj, opts = {}) {
|
|
51854
52517
|
const issues = [];
|
|
52518
|
+
canonicalizeAdjudication(adj);
|
|
51855
52519
|
const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
|
|
51856
52520
|
const packMode = !isCore(adj.standard);
|
|
51857
52521
|
const open = /* @__PURE__ */ new Set();
|
|
@@ -51913,9 +52577,9 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
51913
52577
|
}
|
|
51914
52578
|
} else if (v === "manual") {
|
|
51915
52579
|
if (!it.reason || !MANUAL_REASONS.has(it.reason))
|
|
51916
|
-
issues.push(`criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {
|
|
52580
|
+
issues.push(`criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`);
|
|
51917
52581
|
} else {
|
|
51918
|
-
issues.push(`criterion ${it.criteriaId}: unknown verdict "${String(v)}"`);
|
|
52582
|
+
issues.push(`criterion ${it.criteriaId}: unknown verdict "${String(v)}" \u2014 expected one of ${VERDICTS.join(" | ")}`);
|
|
51919
52583
|
}
|
|
51920
52584
|
for (const rec of it.recommendations ?? []) groundInputs.push({ file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
51921
52585
|
}
|
|
@@ -52173,6 +52837,7 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
52173
52837
|
schemaVersion: SCHEMA_VERSION,
|
|
52174
52838
|
standard: opts.standard,
|
|
52175
52839
|
auditDate: opts.auditDate,
|
|
52840
|
+
contract: adjudicationContract(),
|
|
52176
52841
|
items
|
|
52177
52842
|
};
|
|
52178
52843
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
@@ -52369,10 +53034,10 @@ function applyRawVerdicts(items, verdicts) {
|
|
|
52369
53034
|
}
|
|
52370
53035
|
|
|
52371
53036
|
// src/scan.ts
|
|
52372
|
-
import { execFileSync as
|
|
53037
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
52373
53038
|
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync11, existsSync as existsSync20, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync16 } from "fs";
|
|
52374
53039
|
import { tmpdir as tmpdir2 } from "os";
|
|
52375
|
-
import { join as join33, resolve as
|
|
53040
|
+
import { join as join33, resolve as resolve8 } from "path";
|
|
52376
53041
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
52377
53042
|
|
|
52378
53043
|
// src/axe-map.ts
|
|
@@ -52821,7 +53486,7 @@ ENTRYPOINT ["node", "/app/runner.mjs"]
|
|
|
52821
53486
|
`;
|
|
52822
53487
|
function dockerAvailable() {
|
|
52823
53488
|
try {
|
|
52824
|
-
|
|
53489
|
+
execFileSync6("docker", ["info"], { stdio: "ignore", timeout: 1e4 });
|
|
52825
53490
|
return true;
|
|
52826
53491
|
} catch {
|
|
52827
53492
|
return false;
|
|
@@ -52829,7 +53494,7 @@ function dockerAvailable() {
|
|
|
52829
53494
|
}
|
|
52830
53495
|
function imageExists(tag) {
|
|
52831
53496
|
try {
|
|
52832
|
-
|
|
53497
|
+
execFileSync6("docker", ["image", "inspect", tag], { stdio: "ignore" });
|
|
52833
53498
|
return true;
|
|
52834
53499
|
} catch {
|
|
52835
53500
|
return false;
|
|
@@ -52842,7 +53507,7 @@ function buildImage(tag = IMAGE_TAG) {
|
|
|
52842
53507
|
writeFileSync11(join33(ctx, "runner.mjs"), RUNNER);
|
|
52843
53508
|
writeFileSync11(join33(ctx, "package.json"), PKG);
|
|
52844
53509
|
writeFileSync11(join33(ctx, "Dockerfile"), DOCKERFILE);
|
|
52845
|
-
|
|
53510
|
+
execFileSync6("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
|
|
52846
53511
|
} finally {
|
|
52847
53512
|
rmSync3(ctx, { recursive: true, force: true });
|
|
52848
53513
|
}
|
|
@@ -52861,7 +53526,7 @@ function cleanDynamic(tag = IMAGE_TAG) {
|
|
|
52861
53526
|
let imageRemoved = false;
|
|
52862
53527
|
if (dockerAvailable() && imageExists(tag)) {
|
|
52863
53528
|
try {
|
|
52864
|
-
|
|
53529
|
+
execFileSync6("docker", ["rmi", "-f", tag], { stdio: "ignore" });
|
|
52865
53530
|
imageRemoved = true;
|
|
52866
53531
|
} catch {
|
|
52867
53532
|
}
|
|
@@ -52918,11 +53583,11 @@ var PROBE_FIELDS = [
|
|
|
52918
53583
|
function runRunner(target, isFile, tag, snapshot = true) {
|
|
52919
53584
|
const args2 = ["run", "--rm"];
|
|
52920
53585
|
if (!snapshot) args2.push("-e", "ULTRA11Y_SNAPSHOT=0");
|
|
52921
|
-
if (isFile) args2.push("-v", `${
|
|
53586
|
+
if (isFile) args2.push("-v", `${resolve8(target)}:${MOUNT}:ro`);
|
|
52922
53587
|
args2.push(tag, isFile ? MOUNT : target);
|
|
52923
53588
|
let stdout;
|
|
52924
53589
|
try {
|
|
52925
|
-
stdout =
|
|
53590
|
+
stdout = execFileSync6("docker", args2, { encoding: "utf8", timeout: 24e4, maxBuffer: 192 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
52926
53591
|
} catch (e) {
|
|
52927
53592
|
const err2 = e;
|
|
52928
53593
|
const detail = (err2.stderr ? String(err2.stderr).trim() : "") || err2.message || String(e);
|
|
@@ -53227,7 +53892,7 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
53227
53892
|
// src/scan-local.ts
|
|
53228
53893
|
import { existsSync as existsSync21, statSync as statSync9 } from "fs";
|
|
53229
53894
|
import { createRequire } from "module";
|
|
53230
|
-
import { resolve as
|
|
53895
|
+
import { resolve as resolve9 } from "path";
|
|
53231
53896
|
var LOCAL_ENGINE = "axe-core@playwright (local)";
|
|
53232
53897
|
var LOCAL_TESTED_SCS = ["1.4.4", "1.4.10", "1.4.12", "2.4.7", "1.4.13"];
|
|
53233
53898
|
function localTestedScs(interact) {
|
|
@@ -53237,7 +53902,7 @@ var PW_SPEC = "@playwright/test";
|
|
|
53237
53902
|
var AXE_SPEC = "@axe-core/playwright";
|
|
53238
53903
|
function localAvailable(cwd) {
|
|
53239
53904
|
try {
|
|
53240
|
-
const req = createRequire(
|
|
53905
|
+
const req = createRequire(resolve9(cwd, "package.json"));
|
|
53241
53906
|
req.resolve(PW_SPEC);
|
|
53242
53907
|
req.resolve(AXE_SPEC);
|
|
53243
53908
|
return true;
|
|
@@ -53249,7 +53914,7 @@ function resolveLocalDeps(cwd) {
|
|
|
53249
53914
|
let chromium;
|
|
53250
53915
|
let AxeBuilder;
|
|
53251
53916
|
try {
|
|
53252
|
-
const req = createRequire(
|
|
53917
|
+
const req = createRequire(resolve9(cwd, "package.json"));
|
|
53253
53918
|
const pw = req(PW_SPEC);
|
|
53254
53919
|
const axeMod = req(AXE_SPEC);
|
|
53255
53920
|
chromium = pw.chromium;
|
|
@@ -53728,7 +54393,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
53728
54393
|
const page = await context.newPage();
|
|
53729
54394
|
const empty = [];
|
|
53730
54395
|
try {
|
|
53731
|
-
const url = isFile ? "file://" +
|
|
54396
|
+
const url = isFile ? "file://" + resolve9(target) : target;
|
|
53732
54397
|
await page.goto(url, { waitUntil: "load", timeout: 45e3 });
|
|
53733
54398
|
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
|
|
53734
54399
|
});
|
|
@@ -54193,12 +54858,12 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
54193
54858
|
|
|
54194
54859
|
// src/init.ts
|
|
54195
54860
|
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
54196
|
-
import { execFileSync as
|
|
54861
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
54197
54862
|
import { join as join34, relative as relative4, sep as sep4 } from "path";
|
|
54198
54863
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
54199
54864
|
function repoRoot() {
|
|
54200
54865
|
try {
|
|
54201
|
-
return
|
|
54866
|
+
return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
54202
54867
|
} catch {
|
|
54203
54868
|
return null;
|
|
54204
54869
|
}
|
|
@@ -54916,7 +55581,7 @@ var STR = {
|
|
|
54916
55581
|
function t(lang, key) {
|
|
54917
55582
|
return STR[lang][key];
|
|
54918
55583
|
}
|
|
54919
|
-
var
|
|
55584
|
+
var ICON4 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
54920
55585
|
function auditSummary(r, lang) {
|
|
54921
55586
|
const lines = [];
|
|
54922
55587
|
lines.push(`${t(lang, "summaryTitle")} \u2014 ${r.date}`);
|
|
@@ -54933,7 +55598,7 @@ function auditSummary(r, lang) {
|
|
|
54933
55598
|
} else {
|
|
54934
55599
|
lines.push(`${t(lang, "findingsTitle")} (${r.findings.length}) :`);
|
|
54935
55600
|
for (const f of r.findings.slice(0, 20)) {
|
|
54936
|
-
lines.push(` ${
|
|
55601
|
+
lines.push(` ${ICON4[f.severity]} [${f.criteriaId}] ${f.file}:${f.line} ${resolveMessage(f, lang)}`);
|
|
54937
55602
|
}
|
|
54938
55603
|
if (r.findings.length > 20) lines.push(` \u2026 (+${r.findings.length - 20})`);
|
|
54939
55604
|
}
|
|
@@ -55072,8 +55737,8 @@ function toSarif(result, opts = {}) {
|
|
|
55072
55737
|
|
|
55073
55738
|
// src/annotate.ts
|
|
55074
55739
|
var LEVEL2 = { bloquant: "error", majeur: "warning", mineur: "notice" };
|
|
55075
|
-
var
|
|
55076
|
-
var
|
|
55740
|
+
var ICON5 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
55741
|
+
var SEV_ORDER4 = ["bloquant", "majeur", "mineur"];
|
|
55077
55742
|
function esc(s) {
|
|
55078
55743
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
55079
55744
|
}
|
|
@@ -55167,13 +55832,13 @@ function stepSummary(result, opts = {}) {
|
|
|
55167
55832
|
out2.push(perPageTable(result, standard, lang));
|
|
55168
55833
|
return out2.join("\n");
|
|
55169
55834
|
}
|
|
55170
|
-
const sorted = [...all].sort((a, b) =>
|
|
55835
|
+
const sorted = [...all].sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity));
|
|
55171
55836
|
out2.push(`### ${s.findings} (${sorted.length})`, "");
|
|
55172
55837
|
out2.push(`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} |`, "| --- | --- | --- | --- |");
|
|
55173
55838
|
for (const f of sorted.slice(0, MAX_ROWS)) {
|
|
55174
55839
|
const where = isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`;
|
|
55175
55840
|
const msg = resolveMessage(f, lang).replace(/\|/g, "\\|");
|
|
55176
|
-
out2.push(`| ${
|
|
55841
|
+
out2.push(`| ${ICON5[f.severity]} ${f.severity} | ${criterionLabel2(f, standard)} | \`${where}\` | ${msg} |`);
|
|
55177
55842
|
}
|
|
55178
55843
|
if (sorted.length > MAX_ROWS) out2.push("", s.more(sorted.length - MAX_ROWS));
|
|
55179
55844
|
out2.push("");
|
|
@@ -55636,10 +56301,10 @@ function startDevServer(opts) {
|
|
|
55636
56301
|
}
|
|
55637
56302
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
55638
56303
|
});
|
|
55639
|
-
return new Promise((
|
|
56304
|
+
return new Promise((resolve13, reject) => {
|
|
55640
56305
|
server.once("error", reject);
|
|
55641
56306
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
55642
|
-
|
|
56307
|
+
resolve13({
|
|
55643
56308
|
port: server.address().port,
|
|
55644
56309
|
close: () => new Promise((r) => server.close(() => r()))
|
|
55645
56310
|
});
|
|
@@ -55924,7 +56589,7 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
55924
56589
|
|
|
55925
56590
|
// src/orchestrate.ts
|
|
55926
56591
|
import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync24, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "fs";
|
|
55927
|
-
import { join as join42, resolve as
|
|
56592
|
+
import { join as join42, resolve as resolve10 } from "path";
|
|
55928
56593
|
|
|
55929
56594
|
// src/orchestrate-templates.ts
|
|
55930
56595
|
import { join as join41 } from "path";
|
|
@@ -56135,7 +56800,7 @@ var PHASES = ["adjudicate", "verify-report"];
|
|
|
56135
56800
|
var SMALL_WORKLIST = 3;
|
|
56136
56801
|
var BATCH_SIZE2 = 8;
|
|
56137
56802
|
function listPhases(runDir, engineAbs) {
|
|
56138
|
-
const run2 =
|
|
56803
|
+
const run2 = resolve10(runDir);
|
|
56139
56804
|
const adjPath = join42(run2, "ADJUDICATE.todo.json");
|
|
56140
56805
|
let adjIds = [];
|
|
56141
56806
|
let adjReady = false;
|
|
@@ -56182,7 +56847,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
56182
56847
|
];
|
|
56183
56848
|
}
|
|
56184
56849
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
56185
|
-
const run2 =
|
|
56850
|
+
const run2 = resolve10(runDir);
|
|
56186
56851
|
if (!existsSync28(run2)) {
|
|
56187
56852
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
56188
56853
|
}
|
|
@@ -56255,7 +56920,7 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
56255
56920
|
|
|
56256
56921
|
// src/mcp/handlers.ts
|
|
56257
56922
|
import { existsSync as existsSync29, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
|
|
56258
|
-
import { isAbsolute as isAbsolute3, join as join43, resolve as
|
|
56923
|
+
import { isAbsolute as isAbsolute3, join as join43, resolve as resolve11, sep as sep5 } from "path";
|
|
56259
56924
|
|
|
56260
56925
|
// src/project-lock.ts
|
|
56261
56926
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -56303,7 +56968,7 @@ function positive(v, key) {
|
|
|
56303
56968
|
function requiredCwd(args2, defaults) {
|
|
56304
56969
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
56305
56970
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
56306
|
-
const abs =
|
|
56971
|
+
const abs = resolve11(cwd);
|
|
56307
56972
|
if (!existsSync29(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
56308
56973
|
if (!statSync11(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
56309
56974
|
return abs;
|
|
@@ -56334,6 +56999,8 @@ async function dispatch(name2, args2, cwd) {
|
|
|
56334
56999
|
return handleReport(args2, cwd);
|
|
56335
57000
|
case "ultra11y_prd":
|
|
56336
57001
|
return handlePrd(args2, cwd);
|
|
57002
|
+
case "ultra11y_tickets":
|
|
57003
|
+
return handleTickets(args2, cwd);
|
|
56337
57004
|
case "ultra11y_check":
|
|
56338
57005
|
return handleCheck(args2, cwd);
|
|
56339
57006
|
case "ultra11y_verify":
|
|
@@ -56422,6 +57089,24 @@ function handlePrd(args2, cwd) {
|
|
|
56422
57089
|
...str2(args2.split) === "criterion" ? { grouped_by: "criterion" } : {}
|
|
56423
57090
|
};
|
|
56424
57091
|
}
|
|
57092
|
+
function handleTickets(args2, cwd) {
|
|
57093
|
+
const standard = standardOf(args2);
|
|
57094
|
+
const lang = langOf(args2);
|
|
57095
|
+
const grain = str2(args2.grain) ?? "criterion";
|
|
57096
|
+
const plan = buildTickets(audit(args2, cwd), { grain, standard, lang, baseDir: cwd });
|
|
57097
|
+
if (plan.error === "no-pages") {
|
|
57098
|
+
return { cwd, standard, grain, error: "no-pages", hint: "No page in scope \u2014 capture snapshots (render --e2e) or scan a sample (scan --sample) first." };
|
|
57099
|
+
}
|
|
57100
|
+
return {
|
|
57101
|
+
cwd,
|
|
57102
|
+
standard,
|
|
57103
|
+
grain,
|
|
57104
|
+
dry_run: true,
|
|
57105
|
+
tickets: plan.tickets.map((t2) => ({ title: t2.title, labels: t2.labels, severity: t2.severity, advisory: t2.advisory, scope: t2.scope, body: t2.body })),
|
|
57106
|
+
unattributed: plan.unattributed,
|
|
57107
|
+
to_file_them: `ultra11y tickets --in <audit.json> --provider github|gitlab|jira --grain ${grain}`
|
|
57108
|
+
};
|
|
57109
|
+
}
|
|
56425
57110
|
function handleCheck(args2, cwd) {
|
|
56426
57111
|
const text = reportText(args2, "check");
|
|
56427
57112
|
const res = checkReport(text, standardOf(args2), langOf(args2));
|
|
@@ -56527,7 +57212,7 @@ function handleInit(args2, cwd) {
|
|
|
56527
57212
|
throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
|
|
56528
57213
|
}
|
|
56529
57214
|
const failOn = failOnRaw;
|
|
56530
|
-
const enginePath =
|
|
57215
|
+
const enginePath = resolve11("scripts/ultra11y.mjs");
|
|
56531
57216
|
const written = [];
|
|
56532
57217
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
56533
57218
|
if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
|
|
@@ -56751,6 +57436,26 @@ var TOOLS2 = [
|
|
|
56751
57436
|
required: ["cwd"]
|
|
56752
57437
|
}
|
|
56753
57438
|
},
|
|
57439
|
+
{
|
|
57440
|
+
name: "ultra11y_tickets",
|
|
57441
|
+
title: "Preview the tracker tickets an audit would file",
|
|
57442
|
+
description: "Show the GitHub/GitLab/Jira tickets this audit would open, at a granularity you choose: one per criterion (default), per page, per page+criterion, per file, or one consolidated. Returns the plan ONLY \u2014 it never creates anything; filing stays a deliberate `ultra11y tickets` run.",
|
|
57443
|
+
inputSchema: {
|
|
57444
|
+
type: "object",
|
|
57445
|
+
properties: {
|
|
57446
|
+
cwd: cwdProp,
|
|
57447
|
+
globs: globsProp,
|
|
57448
|
+
standard: standardProp,
|
|
57449
|
+
lang: langProp,
|
|
57450
|
+
grain: {
|
|
57451
|
+
type: "string",
|
|
57452
|
+
enum: ["criterion", "page", "page-criterion", "single", "file"],
|
|
57453
|
+
description: "What ONE ticket is. Default 'criterion'."
|
|
57454
|
+
}
|
|
57455
|
+
},
|
|
57456
|
+
required: ["cwd"]
|
|
57457
|
+
}
|
|
57458
|
+
},
|
|
56754
57459
|
{
|
|
56755
57460
|
name: "ultra11y_criteria",
|
|
56756
57461
|
title: "The offline standards reference",
|
|
@@ -56929,6 +57634,11 @@ var TOOL_META2 = {
|
|
|
56929
57634
|
ultra11y_audit: { openWorld: false },
|
|
56930
57635
|
ultra11y_report: { openWorld: false },
|
|
56931
57636
|
ultra11y_prd: { openWorld: false },
|
|
57637
|
+
// READ-ONLY and closed-world by construction: it returns the ticket PLAN and never files
|
|
57638
|
+
// anything, so it neither touches the project nor reaches a tracker. Filing stays a
|
|
57639
|
+
// deliberate `ultra11y tickets` run — an agent must not be able to open issues in
|
|
57640
|
+
// somebody's tracker off the back of a prompt injection.
|
|
57641
|
+
ultra11y_tickets: { openWorld: false },
|
|
56932
57642
|
ultra11y_criteria: { openWorld: false },
|
|
56933
57643
|
ultra11y_check: { openWorld: false },
|
|
56934
57644
|
ultra11y_verify: { openWorld: false },
|
|
@@ -57097,13 +57807,13 @@ var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
|
57097
57807
|
|
|
57098
57808
|
// src/mcp/resources.ts
|
|
57099
57809
|
import { existsSync as existsSync30, readdirSync as readdirSync6, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
|
|
57100
|
-
import { basename as basename3, dirname as dirname13, join as join44, resolve as
|
|
57810
|
+
import { basename as basename3, dirname as dirname13, join as join44, resolve as resolve12, sep as sep6 } from "path";
|
|
57101
57811
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
57102
57812
|
var SKILL_NAME = "ultra11y";
|
|
57103
57813
|
var URI_SCHEME = "skill://";
|
|
57104
57814
|
function resolveSkillRoot(moduleDir) {
|
|
57105
57815
|
const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
|
|
57106
|
-
const candidates2 = [
|
|
57816
|
+
const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
|
|
57107
57817
|
return candidates2.find((dir) => existsSync30(join44(dir, "SKILL.md")));
|
|
57108
57818
|
}
|
|
57109
57819
|
function listResources(moduleDir) {
|
|
@@ -57126,7 +57836,7 @@ function readResource(uri, moduleDir) {
|
|
|
57126
57836
|
if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
|
|
57127
57837
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
57128
57838
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
57129
|
-
const target =
|
|
57839
|
+
const target = resolve12(root, rel2);
|
|
57130
57840
|
const rootReal = realpathSync5(root);
|
|
57131
57841
|
let targetReal;
|
|
57132
57842
|
try {
|
|
@@ -57407,14 +58117,14 @@ function startHttpServer(opts = {}) {
|
|
|
57407
58117
|
server.requestTimeout = 0;
|
|
57408
58118
|
server.headersTimeout = 6e4;
|
|
57409
58119
|
server.keepAliveTimeout = 12e4;
|
|
57410
|
-
return new Promise((
|
|
58120
|
+
return new Promise((resolve13, reject) => {
|
|
57411
58121
|
server.once("error", reject);
|
|
57412
58122
|
server.listen(opts.port ?? 0, bind, () => {
|
|
57413
58123
|
server.removeListener("error", reject);
|
|
57414
58124
|
const addr2 = server.address();
|
|
57415
58125
|
const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
|
|
57416
58126
|
const host = bind.includes(":") ? `[${bind}]` : bind;
|
|
57417
|
-
|
|
58127
|
+
resolve13({
|
|
57418
58128
|
server,
|
|
57419
58129
|
port,
|
|
57420
58130
|
url: `http://${host}:${port}${MCP_PATH}`,
|
|
@@ -57523,7 +58233,7 @@ function sendJson(res, status, body2, origin, extra = {}) {
|
|
|
57523
58233
|
}
|
|
57524
58234
|
var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
|
|
57525
58235
|
function readBody2(req) {
|
|
57526
|
-
return new Promise((
|
|
58236
|
+
return new Promise((resolve13, reject) => {
|
|
57527
58237
|
const chunks = [];
|
|
57528
58238
|
let size = 0;
|
|
57529
58239
|
let over = false;
|
|
@@ -57547,7 +58257,7 @@ function readBody2(req) {
|
|
|
57547
58257
|
});
|
|
57548
58258
|
req.on("end", () => {
|
|
57549
58259
|
if (over) reject(new Error("too large"));
|
|
57550
|
-
else
|
|
58260
|
+
else resolve13(Buffer.concat(chunks).toString("utf8"));
|
|
57551
58261
|
});
|
|
57552
58262
|
req.on("error", reject);
|
|
57553
58263
|
req.on("aborted", () => reject(new Error("client aborted the request")));
|
|
@@ -57568,7 +58278,9 @@ Usage:
|
|
|
57568
58278
|
ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
|
|
57569
58279
|
ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
|
|
57570
58280
|
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
57571
|
-
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--
|
|
58281
|
+
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
|
|
58282
|
+
ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
|
|
58283
|
+
ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
|
|
57572
58284
|
ultra11y render [<dir>] [--scaffold | --setup | --e2e | --coverage | --storybook] [--runner playwright|cypress|auto] [--captures <dir>] [--out <file>] [--json] [--lang auto|en|fr]
|
|
57573
58285
|
ultra11y criteria [<sc>] [--list] [--standard <pack> [--theme <N>]] [--generate] [--json] [--lang auto|en|fr]
|
|
57574
58286
|
ultra11y criteria --standard <pack> --glossary [<term>] (the terms the standard DEFINES \u2014 its tests depend on them)
|
|
@@ -57624,9 +58336,14 @@ Commands:
|
|
|
57624
58336
|
active standard's vocabulary (RGAA "Th\xE9matique/Crit\xE8re/Test", WCAG core
|
|
57625
58337
|
"Principle\xB7Guideline/Success criterion/Technique") \u2014 theme, criterion +
|
|
57626
58338
|
official wording, test(s), WCAG mapping + level, finding, expected state,
|
|
57627
|
-
verification. --split criterion writes one file per criterion
|
|
57628
|
-
|
|
57629
|
-
|
|
58339
|
+
verification. --split criterion writes one file per criterion.
|
|
58340
|
+
It writes MARKDOWN only \u2014 filing tickets is the 'tickets' command.
|
|
58341
|
+
tickets File the audit as TRACKER TICKETS \u2014 GitHub, GitLab or Jira. Reads an
|
|
58342
|
+
audit.json and pushes; it writes no markdown, exactly as prd/report
|
|
58343
|
+
write markdown and push nothing. --grain chooses what ONE ticket is:
|
|
58344
|
+
per criterion (default), per page, per page+criterion, per file, or one
|
|
58345
|
+
consolidated. De-dupe is by exact title, so re-running never duplicates.
|
|
58346
|
+
--dry-run prints the plan without creating anything.
|
|
57630
58347
|
render Get RENDERED HTML to audit (so component libraries like DSFR are
|
|
57631
58348
|
checked as the HTML they emit, not their JSX sources): detect the
|
|
57632
58349
|
framework and print the build\u2192audit recipe, or --scaffold a
|
|
@@ -57794,8 +58511,14 @@ Options:
|
|
|
57794
58511
|
plus <out>/index.md (recommended past 3\u20134 pages: RGAA is 106 criteria)
|
|
57795
58512
|
--no-technical prd (audit format): omit the technical ticket sections (Partie
|
|
57796
58513
|
technique + Contexte de reproduction) for a pure-auditor block
|
|
57797
|
-
--
|
|
57798
|
-
|
|
58514
|
+
--provider <id> tickets: auto|github|gitlab|jira. 'auto' reads ULTRA11Y_TICKET_PROVIDER,
|
|
58515
|
+
then .ultra11yrc.json, then the git remote (Jira is never auto-detected)
|
|
58516
|
+
--grain <mode> tickets: what one ticket is \u2014 criterion (default) | page | page-criterion | single | file
|
|
58517
|
+
--transport <t> tickets: auto|cli|rest (mcp: stdio|http). 'auto' prefers the CLI (gh/glab),
|
|
58518
|
+
falling back to REST when only a token is available. Jira is REST-only
|
|
58519
|
+
--max-tickets <n> tickets: refuse to file more than n tickets in one run (default 200)
|
|
58520
|
+
tickets --out <dir> also writes the tracker-agnostic set to
|
|
58521
|
+
<dir>/issues-<date>.json, for a workflow engine to file itself
|
|
57799
58522
|
--scaffold render: write an SSR-snapshot harness (default: ultra11y-render.tsx)
|
|
57800
58523
|
--setup render: install the zero-touch test-render capture harvester (.ultra11y/capture-setup.mjs) + print the runner wiring
|
|
57801
58524
|
--coverage render: report rendered-capture coverage (covered vs blind-spot components); with --json emits the coverage object
|
|
@@ -57904,6 +58627,7 @@ var COMMANDS = [
|
|
|
57904
58627
|
"audit",
|
|
57905
58628
|
"report",
|
|
57906
58629
|
"prd",
|
|
58630
|
+
"tickets",
|
|
57907
58631
|
"render",
|
|
57908
58632
|
"criteria",
|
|
57909
58633
|
"check",
|
|
@@ -57929,6 +58653,9 @@ function isCommand(s) {
|
|
|
57929
58653
|
}
|
|
57930
58654
|
var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
57931
58655
|
"out",
|
|
58656
|
+
"provider",
|
|
58657
|
+
"grain",
|
|
58658
|
+
"max-tickets",
|
|
57932
58659
|
"in",
|
|
57933
58660
|
"include",
|
|
57934
58661
|
"exclude",
|
|
@@ -58010,8 +58737,6 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
58010
58737
|
"semantic",
|
|
58011
58738
|
"manual",
|
|
58012
58739
|
"no-technical",
|
|
58013
|
-
"gh-issues",
|
|
58014
|
-
"gh-single",
|
|
58015
58740
|
"override",
|
|
58016
58741
|
"local",
|
|
58017
58742
|
"docker",
|
|
@@ -58313,9 +59038,9 @@ async function cmdDev(p) {
|
|
|
58313
59038
|
}
|
|
58314
59039
|
console.error(fr ? `ultra11y dev : tableau de bord sur http://127.0.0.1:${server.port}` : `ultra11y dev: dashboard on http://127.0.0.1:${server.port}`);
|
|
58315
59040
|
console.error(fr ? "Boucle locale uniquement (l'outil \xE9crit des fichiers). Ctrl-C pour arr\xEAter." : "Loopback only (the tool writes files). Ctrl-C to stop.");
|
|
58316
|
-
await new Promise((
|
|
59041
|
+
await new Promise((resolve13) => {
|
|
58317
59042
|
const stop2 = () => {
|
|
58318
|
-
void server.close().then(
|
|
59043
|
+
void server.close().then(resolve13);
|
|
58319
59044
|
};
|
|
58320
59045
|
process.once("SIGINT", stop2);
|
|
58321
59046
|
process.once("SIGTERM", stop2);
|
|
@@ -58785,31 +59510,207 @@ async function cmdPrd(p) {
|
|
|
58785
59510
|
const paths = writePrd(result, { out: out2, lang, split, format, standard, technical });
|
|
58786
59511
|
const json = p.flags.json === true;
|
|
58787
59512
|
if (!json) for (const path of paths) console.log(path);
|
|
58788
|
-
|
|
58789
|
-
|
|
58790
|
-
|
|
58791
|
-
|
|
58792
|
-
|
|
58793
|
-
|
|
58794
|
-
|
|
58795
|
-
|
|
58796
|
-
|
|
58797
|
-
|
|
58798
|
-
|
|
58799
|
-
|
|
58800
|
-
|
|
58801
|
-
|
|
58802
|
-
|
|
58803
|
-
|
|
58804
|
-
|
|
58805
|
-
|
|
58806
|
-
|
|
58807
|
-
|
|
58808
|
-
|
|
59513
|
+
if (json) console.log(JSON.stringify({ paths, units: prdUnits(result, standard, lang) }, null, 2));
|
|
59514
|
+
return 0;
|
|
59515
|
+
}
|
|
59516
|
+
var REMOVED_FLAGS = {
|
|
59517
|
+
"gh-issues": "ultra11y tickets --in <audit.json> --provider github --grain criterion",
|
|
59518
|
+
"gh-single": "ultra11y tickets --in <audit.json> --provider github --grain single",
|
|
59519
|
+
// The tracker-agnostic export moved WITH ticket filing: `tickets --out` writes the same
|
|
59520
|
+
// envelope (and a superset of the payload) at any grain, so an orchestrator still reads
|
|
59521
|
+
// one stable path — just not from the command that renders documents.
|
|
59522
|
+
"issues-json": "ultra11y tickets --in <audit.json> --out <dir> --grain criterion"
|
|
59523
|
+
};
|
|
59524
|
+
var SECRET_CONFIG_KEYS = ["token", "apiToken", "api_token", "password", "secret"];
|
|
59525
|
+
async function cmdTickets(p) {
|
|
59526
|
+
const standard = stdOf(p, "tickets");
|
|
59527
|
+
if (standard === null) return 2;
|
|
59528
|
+
const inFlag = p.flags.in;
|
|
59529
|
+
if (typeof inFlag !== "string" || !inFlag) {
|
|
59530
|
+
console.error("ultra11y tickets: --in <audit.json> is required ('-' for stdin). Run `audit --out` first.");
|
|
59531
|
+
return 2;
|
|
59532
|
+
}
|
|
59533
|
+
const raw = inFlag === "-" ? await readStdin() : readInputFile(inFlag, "tickets", "--in");
|
|
59534
|
+
if (raw === null) return 2;
|
|
59535
|
+
let result;
|
|
59536
|
+
try {
|
|
59537
|
+
result = JSON.parse(raw);
|
|
59538
|
+
} catch {
|
|
59539
|
+
console.error("ultra11y tickets: --in is not valid JSON (expected an AuditResult).");
|
|
59540
|
+
return 2;
|
|
59541
|
+
}
|
|
59542
|
+
if (!isCurrentAudit(result)) {
|
|
59543
|
+
console.error("ultra11y tickets: input is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
|
|
59544
|
+
return 2;
|
|
59545
|
+
}
|
|
59546
|
+
const lang = resolveLang(p.flags, { audit: result, standard });
|
|
59547
|
+
const fr = lang === "fr";
|
|
59548
|
+
const json = p.flags.json === true;
|
|
59549
|
+
const dryRun = p.flags["dry-run"] === true;
|
|
59550
|
+
let config;
|
|
59551
|
+
try {
|
|
59552
|
+
config = loadConfig(process.cwd())?.tickets;
|
|
59553
|
+
} catch (e) {
|
|
59554
|
+
console.error(`ultra11y tickets: ${e instanceof Error ? e.message : String(e)}`);
|
|
59555
|
+
return 2;
|
|
59556
|
+
}
|
|
59557
|
+
for (const key of SECRET_CONFIG_KEYS) {
|
|
59558
|
+
if (config && key in config) {
|
|
59559
|
+
console.error(
|
|
59560
|
+
`ultra11y tickets: .ultra11yrc.json must not carry a "${key}" \u2014 credentials belong in the environment (see references/tickets.md). Remove it and rotate that value.`
|
|
59561
|
+
);
|
|
59562
|
+
return 2;
|
|
58809
59563
|
}
|
|
58810
59564
|
}
|
|
58811
|
-
|
|
58812
|
-
|
|
59565
|
+
const grainFlag = typeof p.flags.grain === "string" ? p.flags.grain : config?.grain ?? "criterion";
|
|
59566
|
+
if (!ALL_GRAINS.includes(grainFlag)) {
|
|
59567
|
+
console.error(`ultra11y tickets: --grain must be one of ${ALL_GRAINS.join("|")} (got "${grainFlag}").`);
|
|
59568
|
+
return 2;
|
|
59569
|
+
}
|
|
59570
|
+
const transportFlag = typeof p.flags.transport === "string" ? p.flags.transport : config?.transport ?? "auto";
|
|
59571
|
+
if (!["auto", "cli", "rest"].includes(transportFlag)) {
|
|
59572
|
+
console.error(`ultra11y tickets: --transport must be auto, cli or rest (got "${transportFlag}").`);
|
|
59573
|
+
return 2;
|
|
59574
|
+
}
|
|
59575
|
+
const maxTickets = Number.parseInt(String(p.flags["max-tickets"] ?? config?.maxTickets ?? 200), 10);
|
|
59576
|
+
if (!Number.isFinite(maxTickets) || maxTickets < 1) {
|
|
59577
|
+
console.error("ultra11y tickets: --max-tickets must be a positive integer.");
|
|
59578
|
+
return 2;
|
|
59579
|
+
}
|
|
59580
|
+
const providerFlag = typeof p.flags.provider === "string" ? p.flags.provider : "auto";
|
|
59581
|
+
const providerId = providerFlag === "auto" ? autoProvider(process.env, config?.provider) : isProviderId(providerFlag) ? providerFlag : void 0;
|
|
59582
|
+
if (!providerId) {
|
|
59583
|
+
console.error(
|
|
59584
|
+
providerFlag === "auto" ? "ultra11y tickets: could not tell which tracker to file into \u2014 pass --provider github|gitlab|jira (Jira is never auto-detected: it owns no git remote)." : `ultra11y tickets: --provider "${providerFlag}" is not one of ${ALL_PROVIDERS.join("|")}.`
|
|
59585
|
+
);
|
|
59586
|
+
return 2;
|
|
59587
|
+
}
|
|
59588
|
+
const provider = createProvider(providerId, { transport: transportFlag });
|
|
59589
|
+
const format = p.flags.format === "remediation" ? "remediation" : "audit";
|
|
59590
|
+
const plan = buildTickets(result, {
|
|
59591
|
+
grain: grainFlag,
|
|
59592
|
+
standard,
|
|
59593
|
+
lang,
|
|
59594
|
+
format,
|
|
59595
|
+
bodyLimit: provider.capabilities.bodyLimit,
|
|
59596
|
+
baseDir: process.cwd(),
|
|
59597
|
+
technical: p.flags["no-technical"] !== true
|
|
59598
|
+
});
|
|
59599
|
+
if (plan.error === "no-pages") {
|
|
59600
|
+
console.error(
|
|
59601
|
+
fr ? "ultra11y tickets : aucune page dans le p\xE9rim\xE8tre. Capturez des instantan\xE9s (render --e2e) ou scannez un \xE9chantillon (scan --sample) avant d'utiliser --grain page." : "ultra11y tickets: no page in scope. Capture snapshots (render --e2e) or scan a sample (scan --sample) before using --grain page."
|
|
59602
|
+
);
|
|
59603
|
+
return 1;
|
|
59604
|
+
}
|
|
59605
|
+
if (plan.tickets.length > maxTickets) {
|
|
59606
|
+
console.error(
|
|
59607
|
+
fr ? `ultra11y tickets : ${plan.tickets.length} tickets pour --grain ${grainFlag}, au-del\xE0 de la limite de ${maxTickets}. Relancez avec --max-tickets ${plan.tickets.length}, un grain plus grossier, ou --dry-run pour inspecter.` : `ultra11y tickets: ${plan.tickets.length} tickets for --grain ${grainFlag}, past the limit of ${maxTickets}. Re-run with --max-tickets ${plan.tickets.length}, a coarser grain, or --dry-run to inspect.`
|
|
59608
|
+
);
|
|
59609
|
+
return 2;
|
|
59610
|
+
}
|
|
59611
|
+
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
59612
|
+
let setPath;
|
|
59613
|
+
if (ticketsOut) {
|
|
59614
|
+
mkdirSync15(ticketsOut, { recursive: true });
|
|
59615
|
+
setPath = join45(ticketsOut, `issues-${result.date}.json`);
|
|
59616
|
+
const payload = {
|
|
59617
|
+
tool: "ultra11y",
|
|
59618
|
+
kind: "issues",
|
|
59619
|
+
schemaVersion: TICKET_SET_SCHEMA_VERSION,
|
|
59620
|
+
standard,
|
|
59621
|
+
grain: grainFlag,
|
|
59622
|
+
date: result.date,
|
|
59623
|
+
count: plan.tickets.length,
|
|
59624
|
+
issues: plan.tickets
|
|
59625
|
+
};
|
|
59626
|
+
writeFileSync17(setPath, `${JSON.stringify(payload, null, 2)}
|
|
59627
|
+
`);
|
|
59628
|
+
if (!json) console.log(setPath);
|
|
59629
|
+
}
|
|
59630
|
+
if (!plan.tickets.length) {
|
|
59631
|
+
if (!json)
|
|
59632
|
+
console.log(
|
|
59633
|
+
fr ? "ultra11y tickets : rien \xE0 d\xE9poser \u2014 l'audit ne rel\xE8ve aucune non-conformit\xE9." : "ultra11y tickets: nothing to file \u2014 the audit found no non-conformity."
|
|
59634
|
+
);
|
|
59635
|
+
if (json)
|
|
59636
|
+
console.log(
|
|
59637
|
+
JSON.stringify(
|
|
59638
|
+
{
|
|
59639
|
+
provider: providerId,
|
|
59640
|
+
transport: provider.transport,
|
|
59641
|
+
grain: grainFlag,
|
|
59642
|
+
standard,
|
|
59643
|
+
dryRun,
|
|
59644
|
+
...setPath ? { setPath } : {},
|
|
59645
|
+
tickets: [],
|
|
59646
|
+
unattributed: plan.unattributed,
|
|
59647
|
+
result: { created: 0, skipped: 0, failed: 0, createdTitles: [], createdUrls: [], errors: [] }
|
|
59648
|
+
},
|
|
59649
|
+
null,
|
|
59650
|
+
2
|
|
59651
|
+
)
|
|
59652
|
+
);
|
|
59653
|
+
return 0;
|
|
59654
|
+
}
|
|
59655
|
+
if (!provider.available() && !dryRun) {
|
|
59656
|
+
console.error(`ultra11y tickets: ${providerId} is not usable here \u2014 ${provider.unavailableReason()}.`);
|
|
59657
|
+
return 1;
|
|
59658
|
+
}
|
|
59659
|
+
const { plan: planned, result: pushed, dedupeChecked } = await pushTickets(plan.tickets, provider, { dryRun: dryRun || !provider.available() });
|
|
59660
|
+
if (json) {
|
|
59661
|
+
console.log(
|
|
59662
|
+
JSON.stringify(
|
|
59663
|
+
{
|
|
59664
|
+
provider: providerId,
|
|
59665
|
+
transport: provider.transport,
|
|
59666
|
+
grain: grainFlag,
|
|
59667
|
+
standard,
|
|
59668
|
+
dryRun,
|
|
59669
|
+
...setPath ? { setPath } : {},
|
|
59670
|
+
dedupeChecked,
|
|
59671
|
+
tickets: planned.map(({ ticket, action }) => ({
|
|
59672
|
+
title: ticket.title,
|
|
59673
|
+
labels: ticket.labels,
|
|
59674
|
+
severity: ticket.severity,
|
|
59675
|
+
advisory: ticket.advisory,
|
|
59676
|
+
scope: ticket.scope,
|
|
59677
|
+
bodyChars: ticket.body.length,
|
|
59678
|
+
action,
|
|
59679
|
+
// The body rides along ONLY under --dry-run: an agent inspecting the plan wants
|
|
59680
|
+
// it, a 200-ticket push payload does not.
|
|
59681
|
+
...dryRun ? { body: ticket.body } : {}
|
|
59682
|
+
})),
|
|
59683
|
+
unattributed: plan.unattributed,
|
|
59684
|
+
result: pushed
|
|
59685
|
+
},
|
|
59686
|
+
null,
|
|
59687
|
+
2
|
|
59688
|
+
)
|
|
59689
|
+
);
|
|
59690
|
+
} else if (dryRun || !provider.available()) {
|
|
59691
|
+
const create = planned.filter((x) => x.action === "create").length;
|
|
59692
|
+
console.log(
|
|
59693
|
+
fr ? `ultra11y tickets : simulation (${providerId}/${provider.transport}, grain ${grainFlag}) \u2014 ${create} \xE0 cr\xE9er, ${pushed.skipped} d\xE9j\xE0 pr\xE9sent(s).` : `ultra11y tickets: dry run (${providerId}/${provider.transport}, grain ${grainFlag}) \u2014 ${create} to create, ${pushed.skipped} already there.`
|
|
59694
|
+
);
|
|
59695
|
+
for (const { ticket, action } of planned) console.log(` ${action === "create" ? "+" : "="} ${ticket.title}`);
|
|
59696
|
+
if (!dedupeChecked)
|
|
59697
|
+
console.error(
|
|
59698
|
+
fr ? "ultra11y tickets : l'existant n'a pas pu \xEAtre list\xE9 \u2014 le compte \xAB d\xE9j\xE0 pr\xE9sent \xBB n'est pas v\xE9rifi\xE9." : 'ultra11y tickets: could not list existing tickets \u2014 the "already there" count is unverified.'
|
|
59699
|
+
);
|
|
59700
|
+
if (!provider.available()) console.error(`ultra11y tickets: ${providerId} \u2014 ${provider.unavailableReason()}.`);
|
|
59701
|
+
} else {
|
|
59702
|
+
console.log(
|
|
59703
|
+
fr ? `ultra11y tickets : ${providerId} \u2014 ${pushed.created} cr\xE9\xE9(s), ${pushed.skipped} d\xE9j\xE0 pr\xE9sent(s)${pushed.failed ? `, ${pushed.failed} en \xE9chec` : ""}.` : `ultra11y tickets: ${providerId} \u2014 ${pushed.created} created, ${pushed.skipped} already there${pushed.failed ? `, ${pushed.failed} failed` : ""}.`
|
|
59704
|
+
);
|
|
59705
|
+
for (const u of pushed.createdUrls) console.log(` ${u}`);
|
|
59706
|
+
for (const e of pushed.errors) console.error(fr ? `ultra11y tickets : \xE9chec \u2014 ${e}` : `ultra11y tickets: failed \u2014 ${e}`);
|
|
59707
|
+
if (plan.unattributed) {
|
|
59708
|
+
console.error(
|
|
59709
|
+
fr ? `ultra11y tickets : ${plan.unattributed} constat(s) non rattach\xE9(s) \xE0 une page \u2014 d\xE9pos\xE9s dans leur propre ticket, jamais r\xE9partis d'office.` : `ultra11y tickets: ${plan.unattributed} finding(s) attributed to no page \u2014 filed as their own ticket, never spread.`
|
|
59710
|
+
);
|
|
59711
|
+
}
|
|
59712
|
+
}
|
|
59713
|
+
return pushed.failed > 0 ? 1 : 0;
|
|
58813
59714
|
}
|
|
58814
59715
|
function depsAt(root) {
|
|
58815
59716
|
const pkgPath = join45(root, "package.json");
|
|
@@ -59204,7 +60105,22 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
59204
60105
|
mkdirSync15(out2, { recursive: true });
|
|
59205
60106
|
const auditPath = join45(out2, "audit-latest.json");
|
|
59206
60107
|
writeFileSync17(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
59207
|
-
if (p.flags.json)
|
|
60108
|
+
if (p.flags.json)
|
|
60109
|
+
console.log(
|
|
60110
|
+
JSON.stringify(
|
|
60111
|
+
{
|
|
60112
|
+
ok: true,
|
|
60113
|
+
auditPath,
|
|
60114
|
+
applied: r.applied,
|
|
60115
|
+
stillManual: r.stillManual,
|
|
60116
|
+
conformancePct: r.audit.conformancePct,
|
|
60117
|
+
findings: r.audit.findings.length,
|
|
60118
|
+
grounding: r.grounding
|
|
60119
|
+
},
|
|
60120
|
+
null,
|
|
60121
|
+
2
|
|
60122
|
+
)
|
|
60123
|
+
);
|
|
59208
60124
|
else
|
|
59209
60125
|
console.log(
|
|
59210
60126
|
lang === "fr" ? `\u2713 ${r.applied} crit\xE8re(s) adjug\xE9(s), ${r.stillManual} laiss\xE9(s) en r\xE9siduel \u2192 ${auditPath}` : `\u2713 ${r.applied} criterion(ia) adjudicated, ${r.stillManual} left residual \u2192 ${auditPath}`
|
|
@@ -59758,13 +60674,25 @@ async function main(argv) {
|
|
|
59758
60674
|
console.log(HELP2);
|
|
59759
60675
|
return 0;
|
|
59760
60676
|
}
|
|
60677
|
+
for (const f of p.unknown) {
|
|
60678
|
+
const replacement = REMOVED_FLAGS[f];
|
|
60679
|
+
if (replacement) {
|
|
60680
|
+
console.error(`ultra11y: --${f} was removed in v3 \u2014 ticket creation is now its own command. Use: ${replacement}`);
|
|
60681
|
+
return 2;
|
|
60682
|
+
}
|
|
60683
|
+
}
|
|
59761
60684
|
for (const f of p.unknown) console.error(`ultra11y: unknown flag --${f} (ignored). Run \`ultra11y --help\`.`);
|
|
59762
60685
|
const ENUM_FLAGS = {
|
|
59763
60686
|
lang: ["auto", "en", "fr"],
|
|
59764
60687
|
dedup: ["exact", "normalized", "off"],
|
|
59765
60688
|
format: ["audit", "doc", "remediation", "sarif", "github", "grid", "report"],
|
|
59766
60689
|
split: ["criterion", "page"],
|
|
59767
|
-
runtime: ["auto", "local", "docker"]
|
|
60690
|
+
runtime: ["auto", "local", "docker"],
|
|
60691
|
+
provider: ["auto", "github", "gitlab", "jira"],
|
|
60692
|
+
grain: ["criterion", "page", "page-criterion", "single", "file"],
|
|
60693
|
+
// The union over every command that takes it: `mcp` serves stdio|http, `tickets`
|
|
60694
|
+
// picks cli|rest. Listing one command's values made the guard cry wolf on the other.
|
|
60695
|
+
transport: ["stdio", "http", "auto", "cli", "rest"]
|
|
59768
60696
|
};
|
|
59769
60697
|
for (const [flag, allowed] of Object.entries(ENUM_FLAGS)) {
|
|
59770
60698
|
const v = p.flags[flag];
|
|
@@ -59782,6 +60710,8 @@ async function main(argv) {
|
|
|
59782
60710
|
return cmdAudit(p);
|
|
59783
60711
|
case "report":
|
|
59784
60712
|
return cmdReport(p);
|
|
60713
|
+
case "tickets":
|
|
60714
|
+
return cmdTickets(p);
|
|
59785
60715
|
case "prd":
|
|
59786
60716
|
return cmdPrd(p);
|
|
59787
60717
|
case "render":
|
|
@@ -59850,6 +60780,7 @@ if (isInvokedDirectly()) {
|
|
|
59850
60780
|
}
|
|
59851
60781
|
export {
|
|
59852
60782
|
COMMANDS,
|
|
60783
|
+
REMOVED_FLAGS,
|
|
59853
60784
|
main,
|
|
59854
60785
|
parseArgs
|
|
59855
60786
|
};
|