ultra11y 2.32.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +14 -2
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1133 -222
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1133 -222
- package/skills/ultra11y/SKILL.md +17 -11
- package/skills/ultra11y/references/audit.md +1 -1
- package/skills/ultra11y/references/ci.md +63 -0
- package/skills/ultra11y/references/judgment.md +8 -0
- package/skills/ultra11y/references/methodology.md +1 -1
- package/skills/ultra11y/references/orchestrators.md +26 -11
- package/skills/ultra11y/references/prd.md +9 -36
- package/skills/ultra11y/references/tickets.md +156 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +1133 -222
- package/src/cli.ts +386 -63
- 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 -333
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 = "
|
|
9
|
+
var VERSION = "3.0.0";
|
|
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,137 +49784,662 @@ function issueBody(unit, lang, standard = "wcag", format = "audit") {
|
|
|
49698
49784
|
}
|
|
49699
49785
|
return lines.join("\n");
|
|
49700
49786
|
}
|
|
49701
|
-
function
|
|
49702
|
-
|
|
49703
|
-
return unit
|
|
49704
|
-
}
|
|
49705
|
-
function issueSet(units, lang, standard = "wcag", format = "audit") {
|
|
49706
|
-
const { label } = standardTag(standard);
|
|
49707
|
-
return units.map((u) => ({
|
|
49708
|
-
criteriaId: u.criteriaId,
|
|
49709
|
-
title: issueTitle(u, label),
|
|
49710
|
-
body: issueBody(u, lang, standard, format),
|
|
49711
|
-
labels: issueLabels(u, standard),
|
|
49712
|
-
severity: u.severity,
|
|
49713
|
-
advisory: u.advisory === true,
|
|
49714
|
-
occurrences: u.findings.map((f) => ({ file: f.file, line: f.line, selector: f.selectorHint, message: resolveMessage(f, lang) }))
|
|
49715
|
-
}));
|
|
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();
|
|
49716
49790
|
}
|
|
49717
|
-
function
|
|
49718
|
-
const
|
|
49719
|
-
|
|
49720
|
-
|
|
49721
|
-
|
|
49722
|
-
|
|
49723
|
-
|
|
49724
|
-
|
|
49725
|
-
|
|
49726
|
-
|
|
49727
|
-
|
|
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
|
+
});
|
|
49728
49899
|
}
|
|
49900
|
+
return { tickets: tickets2, unattributed: 0 };
|
|
49729
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
|
+
});
|
|
49954
|
+
}
|
|
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: [] };
|
|
49730
49980
|
}
|
|
49731
49981
|
function recordFailure(result, reason) {
|
|
49732
49982
|
result.failed++;
|
|
49733
49983
|
if (reason && !result.errors.includes(reason)) result.errors.push(reason);
|
|
49734
49984
|
}
|
|
49735
|
-
function
|
|
49736
|
-
const
|
|
49737
|
-
|
|
49738
|
-
|
|
49739
|
-
|
|
49740
|
-
|
|
49741
|
-
|
|
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") {
|
|
49742
50003
|
result.skipped++;
|
|
49743
50004
|
continue;
|
|
49744
50005
|
}
|
|
49745
|
-
|
|
49746
|
-
|
|
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) {
|
|
49747
50013
|
result.created++;
|
|
49748
|
-
result.createdTitles.push(
|
|
49749
|
-
|
|
50014
|
+
result.createdTitles.push(ticket.title);
|
|
50015
|
+
if (outcome2.url) result.createdUrls.push(outcome2.url);
|
|
49750
50016
|
} else {
|
|
49751
|
-
recordFailure(result,
|
|
50017
|
+
recordFailure(result, outcome2.reason);
|
|
49752
50018
|
}
|
|
49753
50019
|
}
|
|
49754
|
-
return result;
|
|
50020
|
+
return { plan, result, dedupeChecked };
|
|
49755
50021
|
}
|
|
49756
|
-
|
|
49757
|
-
|
|
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;
|
|
49758
50045
|
}
|
|
49759
|
-
function
|
|
49760
|
-
|
|
49761
|
-
|
|
49762
|
-
|
|
49763
|
-
|
|
49764
|
-
|
|
49765
|
-
|
|
49766
|
-
|
|
49767
|
-
|
|
49768
|
-
|
|
49769
|
-
|
|
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 };
|
|
49770
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));
|
|
49771
50086
|
}
|
|
49772
|
-
return lines.join("\n");
|
|
49773
50087
|
}
|
|
49774
|
-
|
|
49775
|
-
|
|
49776
|
-
|
|
49777
|
-
|
|
49778
|
-
|
|
49779
|
-
|
|
49780
|
-
|
|
49781
|
-
|
|
49782
|
-
|
|
49783
|
-
const severity = [...units].sort((a, b) => SEV_RANK2[a.severity] - SEV_RANK2[b.severity])[0].severity;
|
|
49784
|
-
const r = createIssue(title2, singleIssueBody(units, lang, standard, format), ["accessibility", tag, severity]);
|
|
49785
|
-
if (r.ok) {
|
|
49786
|
-
result.created = 1;
|
|
49787
|
-
result.createdTitles.push(title2);
|
|
49788
|
-
} else {
|
|
49789
|
-
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;
|
|
49790
50097
|
}
|
|
49791
|
-
return result;
|
|
49792
50098
|
}
|
|
49793
|
-
function
|
|
49794
|
-
|
|
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;
|
|
49795
50115
|
}
|
|
49796
|
-
|
|
49797
|
-
|
|
49798
|
-
|
|
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
|
+
};
|
|
49799
50144
|
}
|
|
49800
|
-
function
|
|
49801
|
-
const
|
|
49802
|
-
|
|
49803
|
-
const
|
|
49804
|
-
|
|
49805
|
-
|
|
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
|
+
};
|
|
49806
50225
|
}
|
|
49807
|
-
|
|
49808
|
-
|
|
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"] });
|
|
49809
50232
|
}
|
|
49810
|
-
function
|
|
49811
|
-
const pr = prNumberFromEnv();
|
|
49812
|
-
if (pr === void 0) return { ok: true, action: "skipped", reason: "not a pull-request run" };
|
|
49813
|
-
const marker = COMMENT_MARKER(standard);
|
|
49814
|
-
const body2 = stickyBody(markdown, standard);
|
|
50233
|
+
function glabAvailable() {
|
|
49815
50234
|
try {
|
|
49816
|
-
|
|
49817
|
-
|
|
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) {
|
|
49818
50296
|
try {
|
|
49819
|
-
const
|
|
49820
|
-
|
|
49821
|
-
|
|
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) };
|
|
49822
50307
|
}
|
|
49823
|
-
|
|
49824
|
-
|
|
49825
|
-
|
|
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") };
|
|
49826
50315
|
}
|
|
49827
|
-
gh(["api", "--method", "POST", `repos/${repo}/issues/${pr}/comments`, "-f", `body=${body2}`]);
|
|
49828
|
-
return { ok: true, action: "created" };
|
|
49829
|
-
} catch (e) {
|
|
49830
|
-
return { ok: false, action: "skipped", reason: ghErrorReason(e) };
|
|
49831
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();
|
|
49832
50443
|
}
|
|
49833
50444
|
|
|
49834
50445
|
// src/render.ts
|
|
@@ -49860,7 +50471,7 @@ function detectFrameworks(deps, has2) {
|
|
|
49860
50471
|
for (const [pkg, label] of Object.entries(KNOWN_LIBS)) if (dep(pkg)) componentLibraries.push(label);
|
|
49861
50472
|
return { frameworks, componentLibraries };
|
|
49862
50473
|
}
|
|
49863
|
-
var
|
|
50474
|
+
var L6 = {
|
|
49864
50475
|
fr: {
|
|
49865
50476
|
title: "Obtenir du HTML rendu \xE0 auditer (render)",
|
|
49866
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.",
|
|
@@ -49891,7 +50502,7 @@ var L5 = {
|
|
|
49891
50502
|
}
|
|
49892
50503
|
};
|
|
49893
50504
|
function renderPlan(d, lang = "fr") {
|
|
49894
|
-
const s =
|
|
50505
|
+
const s = L6[lang];
|
|
49895
50506
|
const out2 = [`# ${s.title}`, "", `> ${s.why}`, ""];
|
|
49896
50507
|
if (d.componentLibraries.length) out2.push(`> \u{1F9E9} ${s.libNote(d.componentLibraries.join(", "))}`, "");
|
|
49897
50508
|
out2.push(`## ${s.detected}`, "");
|
|
@@ -50436,7 +51047,7 @@ function writeWorklist(items, outDir, semantic, standard = "wcag", lang = "en")
|
|
|
50436
51047
|
|
|
50437
51048
|
// src/grounding.ts
|
|
50438
51049
|
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
50439
|
-
import { resolve as
|
|
51050
|
+
import { resolve as resolve7 } from "path";
|
|
50440
51051
|
var WINDOW = 10;
|
|
50441
51052
|
var norm2 = (s) => s.replace(/\s+/g, " ").trim();
|
|
50442
51053
|
var isUnresolvable = (file) => !file || file === "-" || file === "<stdin>" || file === "stdin";
|
|
@@ -50453,7 +51064,7 @@ function selectorProbes(selector) {
|
|
|
50453
51064
|
}
|
|
50454
51065
|
function groundFinding(g, opts = {}) {
|
|
50455
51066
|
if (isUnresolvable(g.file)) return { ok: true, moved: false };
|
|
50456
|
-
const path =
|
|
51067
|
+
const path = resolve7(opts.cwd ?? process.cwd(), g.file);
|
|
50457
51068
|
if (!existsSync17(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
|
|
50458
51069
|
let text;
|
|
50459
51070
|
try {
|
|
@@ -50508,7 +51119,7 @@ function isPagesReport(md) {
|
|
|
50508
51119
|
return md.slice(0, 400).includes(`<!-- ${PAGES_REPORT_MARKER}`);
|
|
50509
51120
|
}
|
|
50510
51121
|
var MARK2 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
50511
|
-
var
|
|
51122
|
+
var L7 = {
|
|
50512
51123
|
fr: {
|
|
50513
51124
|
docTitle: "Rapport d'accessibilit\xE9 page par page",
|
|
50514
51125
|
indexTitle: "Rapport page par page \u2014 index",
|
|
@@ -50628,7 +51239,7 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
50628
51239
|
const standard = opts.standard ?? CORE2;
|
|
50629
51240
|
const lang = opts.lang ?? "en";
|
|
50630
51241
|
const h = opts.heading ?? "##";
|
|
50631
|
-
const s =
|
|
51242
|
+
const s = L7[lang];
|
|
50632
51243
|
const out2 = [];
|
|
50633
51244
|
out2.push(`${h} ${page.name}${page.auth ? " \u{1F512}" : ""}`, "");
|
|
50634
51245
|
const meta2 = [];
|
|
@@ -50674,7 +51285,7 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
50674
51285
|
return out2.join("\n");
|
|
50675
51286
|
}
|
|
50676
51287
|
function header2(result, pages, standard, lang, title2) {
|
|
50677
|
-
const s =
|
|
51288
|
+
const s = L7[lang];
|
|
50678
51289
|
return [
|
|
50679
51290
|
pagesReportMarker(standard),
|
|
50680
51291
|
"",
|
|
@@ -50689,7 +51300,7 @@ function header2(result, pages, standard, lang, title2) {
|
|
|
50689
51300
|
function renderPagesIndex(result, pages, opts = {}) {
|
|
50690
51301
|
const standard = opts.standard ?? CORE2;
|
|
50691
51302
|
const lang = opts.lang ?? "en";
|
|
50692
|
-
const s =
|
|
51303
|
+
const s = L7[lang];
|
|
50693
51304
|
const out2 = header2(result, pages, standard, lang, s.indexTitle);
|
|
50694
51305
|
out2.push(`> ${s.indexNote}`, "");
|
|
50695
51306
|
out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rate} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
|
|
@@ -50717,7 +51328,7 @@ function renderPagesDocument(result, pages, opts = {}) {
|
|
|
50717
51328
|
function renderPageDocument(result, page, opts = {}) {
|
|
50718
51329
|
const standard = opts.standard ?? CORE2;
|
|
50719
51330
|
const lang = opts.lang ?? "en";
|
|
50720
|
-
const s =
|
|
51331
|
+
const s = L7[lang];
|
|
50721
51332
|
const out2 = [pagesReportMarker(standard), "", `# ${s.docTitle} \u2014 ${page.name}`, "", `- **${s.date}** : ${result.date}`, ""];
|
|
50722
51333
|
out2.push(renderPageReport(result, page, { ...opts, standard, lang, heading: "##" }));
|
|
50723
51334
|
return out2.join("\n");
|
|
@@ -52423,10 +53034,10 @@ function applyRawVerdicts(items, verdicts) {
|
|
|
52423
53034
|
}
|
|
52424
53035
|
|
|
52425
53036
|
// src/scan.ts
|
|
52426
|
-
import { execFileSync as
|
|
53037
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
52427
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";
|
|
52428
53039
|
import { tmpdir as tmpdir2 } from "os";
|
|
52429
|
-
import { join as join33, resolve as
|
|
53040
|
+
import { join as join33, resolve as resolve8 } from "path";
|
|
52430
53041
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
52431
53042
|
|
|
52432
53043
|
// src/axe-map.ts
|
|
@@ -52875,7 +53486,7 @@ ENTRYPOINT ["node", "/app/runner.mjs"]
|
|
|
52875
53486
|
`;
|
|
52876
53487
|
function dockerAvailable() {
|
|
52877
53488
|
try {
|
|
52878
|
-
|
|
53489
|
+
execFileSync6("docker", ["info"], { stdio: "ignore", timeout: 1e4 });
|
|
52879
53490
|
return true;
|
|
52880
53491
|
} catch {
|
|
52881
53492
|
return false;
|
|
@@ -52883,7 +53494,7 @@ function dockerAvailable() {
|
|
|
52883
53494
|
}
|
|
52884
53495
|
function imageExists(tag) {
|
|
52885
53496
|
try {
|
|
52886
|
-
|
|
53497
|
+
execFileSync6("docker", ["image", "inspect", tag], { stdio: "ignore" });
|
|
52887
53498
|
return true;
|
|
52888
53499
|
} catch {
|
|
52889
53500
|
return false;
|
|
@@ -52896,7 +53507,7 @@ function buildImage(tag = IMAGE_TAG) {
|
|
|
52896
53507
|
writeFileSync11(join33(ctx, "runner.mjs"), RUNNER);
|
|
52897
53508
|
writeFileSync11(join33(ctx, "package.json"), PKG);
|
|
52898
53509
|
writeFileSync11(join33(ctx, "Dockerfile"), DOCKERFILE);
|
|
52899
|
-
|
|
53510
|
+
execFileSync6("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
|
|
52900
53511
|
} finally {
|
|
52901
53512
|
rmSync3(ctx, { recursive: true, force: true });
|
|
52902
53513
|
}
|
|
@@ -52915,7 +53526,7 @@ function cleanDynamic(tag = IMAGE_TAG) {
|
|
|
52915
53526
|
let imageRemoved = false;
|
|
52916
53527
|
if (dockerAvailable() && imageExists(tag)) {
|
|
52917
53528
|
try {
|
|
52918
|
-
|
|
53529
|
+
execFileSync6("docker", ["rmi", "-f", tag], { stdio: "ignore" });
|
|
52919
53530
|
imageRemoved = true;
|
|
52920
53531
|
} catch {
|
|
52921
53532
|
}
|
|
@@ -52972,11 +53583,11 @@ var PROBE_FIELDS = [
|
|
|
52972
53583
|
function runRunner(target, isFile, tag, snapshot = true) {
|
|
52973
53584
|
const args2 = ["run", "--rm"];
|
|
52974
53585
|
if (!snapshot) args2.push("-e", "ULTRA11Y_SNAPSHOT=0");
|
|
52975
|
-
if (isFile) args2.push("-v", `${
|
|
53586
|
+
if (isFile) args2.push("-v", `${resolve8(target)}:${MOUNT}:ro`);
|
|
52976
53587
|
args2.push(tag, isFile ? MOUNT : target);
|
|
52977
53588
|
let stdout;
|
|
52978
53589
|
try {
|
|
52979
|
-
stdout =
|
|
53590
|
+
stdout = execFileSync6("docker", args2, { encoding: "utf8", timeout: 24e4, maxBuffer: 192 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
52980
53591
|
} catch (e) {
|
|
52981
53592
|
const err2 = e;
|
|
52982
53593
|
const detail = (err2.stderr ? String(err2.stderr).trim() : "") || err2.message || String(e);
|
|
@@ -53281,7 +53892,7 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
53281
53892
|
// src/scan-local.ts
|
|
53282
53893
|
import { existsSync as existsSync21, statSync as statSync9 } from "fs";
|
|
53283
53894
|
import { createRequire } from "module";
|
|
53284
|
-
import { resolve as
|
|
53895
|
+
import { resolve as resolve9 } from "path";
|
|
53285
53896
|
var LOCAL_ENGINE = "axe-core@playwright (local)";
|
|
53286
53897
|
var LOCAL_TESTED_SCS = ["1.4.4", "1.4.10", "1.4.12", "2.4.7", "1.4.13"];
|
|
53287
53898
|
function localTestedScs(interact) {
|
|
@@ -53291,7 +53902,7 @@ var PW_SPEC = "@playwright/test";
|
|
|
53291
53902
|
var AXE_SPEC = "@axe-core/playwright";
|
|
53292
53903
|
function localAvailable(cwd) {
|
|
53293
53904
|
try {
|
|
53294
|
-
const req = createRequire(
|
|
53905
|
+
const req = createRequire(resolve9(cwd, "package.json"));
|
|
53295
53906
|
req.resolve(PW_SPEC);
|
|
53296
53907
|
req.resolve(AXE_SPEC);
|
|
53297
53908
|
return true;
|
|
@@ -53303,7 +53914,7 @@ function resolveLocalDeps(cwd) {
|
|
|
53303
53914
|
let chromium;
|
|
53304
53915
|
let AxeBuilder;
|
|
53305
53916
|
try {
|
|
53306
|
-
const req = createRequire(
|
|
53917
|
+
const req = createRequire(resolve9(cwd, "package.json"));
|
|
53307
53918
|
const pw = req(PW_SPEC);
|
|
53308
53919
|
const axeMod = req(AXE_SPEC);
|
|
53309
53920
|
chromium = pw.chromium;
|
|
@@ -53782,7 +54393,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
53782
54393
|
const page = await context.newPage();
|
|
53783
54394
|
const empty = [];
|
|
53784
54395
|
try {
|
|
53785
|
-
const url = isFile ? "file://" +
|
|
54396
|
+
const url = isFile ? "file://" + resolve9(target) : target;
|
|
53786
54397
|
await page.goto(url, { waitUntil: "load", timeout: 45e3 });
|
|
53787
54398
|
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
|
|
53788
54399
|
});
|
|
@@ -54247,12 +54858,12 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
54247
54858
|
|
|
54248
54859
|
// src/init.ts
|
|
54249
54860
|
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
54250
|
-
import { execFileSync as
|
|
54861
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
54251
54862
|
import { join as join34, relative as relative4, sep as sep4 } from "path";
|
|
54252
54863
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
54253
54864
|
function repoRoot() {
|
|
54254
54865
|
try {
|
|
54255
|
-
return
|
|
54866
|
+
return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
54256
54867
|
} catch {
|
|
54257
54868
|
return null;
|
|
54258
54869
|
}
|
|
@@ -54970,7 +55581,7 @@ var STR = {
|
|
|
54970
55581
|
function t(lang, key) {
|
|
54971
55582
|
return STR[lang][key];
|
|
54972
55583
|
}
|
|
54973
|
-
var
|
|
55584
|
+
var ICON4 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
54974
55585
|
function auditSummary(r, lang) {
|
|
54975
55586
|
const lines = [];
|
|
54976
55587
|
lines.push(`${t(lang, "summaryTitle")} \u2014 ${r.date}`);
|
|
@@ -54987,7 +55598,7 @@ function auditSummary(r, lang) {
|
|
|
54987
55598
|
} else {
|
|
54988
55599
|
lines.push(`${t(lang, "findingsTitle")} (${r.findings.length}) :`);
|
|
54989
55600
|
for (const f of r.findings.slice(0, 20)) {
|
|
54990
|
-
lines.push(` ${
|
|
55601
|
+
lines.push(` ${ICON4[f.severity]} [${f.criteriaId}] ${f.file}:${f.line} ${resolveMessage(f, lang)}`);
|
|
54991
55602
|
}
|
|
54992
55603
|
if (r.findings.length > 20) lines.push(` \u2026 (+${r.findings.length - 20})`);
|
|
54993
55604
|
}
|
|
@@ -55126,8 +55737,8 @@ function toSarif(result, opts = {}) {
|
|
|
55126
55737
|
|
|
55127
55738
|
// src/annotate.ts
|
|
55128
55739
|
var LEVEL2 = { bloquant: "error", majeur: "warning", mineur: "notice" };
|
|
55129
|
-
var
|
|
55130
|
-
var
|
|
55740
|
+
var ICON5 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
55741
|
+
var SEV_ORDER4 = ["bloquant", "majeur", "mineur"];
|
|
55131
55742
|
function esc(s) {
|
|
55132
55743
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
55133
55744
|
}
|
|
@@ -55221,13 +55832,13 @@ function stepSummary(result, opts = {}) {
|
|
|
55221
55832
|
out2.push(perPageTable(result, standard, lang));
|
|
55222
55833
|
return out2.join("\n");
|
|
55223
55834
|
}
|
|
55224
|
-
const sorted = [...all].sort((a, b) =>
|
|
55835
|
+
const sorted = [...all].sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity));
|
|
55225
55836
|
out2.push(`### ${s.findings} (${sorted.length})`, "");
|
|
55226
55837
|
out2.push(`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} |`, "| --- | --- | --- | --- |");
|
|
55227
55838
|
for (const f of sorted.slice(0, MAX_ROWS)) {
|
|
55228
55839
|
const where = isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`;
|
|
55229
55840
|
const msg = resolveMessage(f, lang).replace(/\|/g, "\\|");
|
|
55230
|
-
out2.push(`| ${
|
|
55841
|
+
out2.push(`| ${ICON5[f.severity]} ${f.severity} | ${criterionLabel2(f, standard)} | \`${where}\` | ${msg} |`);
|
|
55231
55842
|
}
|
|
55232
55843
|
if (sorted.length > MAX_ROWS) out2.push("", s.more(sorted.length - MAX_ROWS));
|
|
55233
55844
|
out2.push("");
|
|
@@ -55690,10 +56301,10 @@ function startDevServer(opts) {
|
|
|
55690
56301
|
}
|
|
55691
56302
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
55692
56303
|
});
|
|
55693
|
-
return new Promise((
|
|
56304
|
+
return new Promise((resolve13, reject) => {
|
|
55694
56305
|
server.once("error", reject);
|
|
55695
56306
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
55696
|
-
|
|
56307
|
+
resolve13({
|
|
55697
56308
|
port: server.address().port,
|
|
55698
56309
|
close: () => new Promise((r) => server.close(() => r()))
|
|
55699
56310
|
});
|
|
@@ -55978,7 +56589,7 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
55978
56589
|
|
|
55979
56590
|
// src/orchestrate.ts
|
|
55980
56591
|
import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync24, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "fs";
|
|
55981
|
-
import { join as join42, resolve as
|
|
56592
|
+
import { join as join42, resolve as resolve10 } from "path";
|
|
55982
56593
|
|
|
55983
56594
|
// src/orchestrate-templates.ts
|
|
55984
56595
|
import { join as join41 } from "path";
|
|
@@ -56189,7 +56800,7 @@ var PHASES = ["adjudicate", "verify-report"];
|
|
|
56189
56800
|
var SMALL_WORKLIST = 3;
|
|
56190
56801
|
var BATCH_SIZE2 = 8;
|
|
56191
56802
|
function listPhases(runDir, engineAbs) {
|
|
56192
|
-
const run2 =
|
|
56803
|
+
const run2 = resolve10(runDir);
|
|
56193
56804
|
const adjPath = join42(run2, "ADJUDICATE.todo.json");
|
|
56194
56805
|
let adjIds = [];
|
|
56195
56806
|
let adjReady = false;
|
|
@@ -56236,7 +56847,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
56236
56847
|
];
|
|
56237
56848
|
}
|
|
56238
56849
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
56239
|
-
const run2 =
|
|
56850
|
+
const run2 = resolve10(runDir);
|
|
56240
56851
|
if (!existsSync28(run2)) {
|
|
56241
56852
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
56242
56853
|
}
|
|
@@ -56309,7 +56920,7 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
56309
56920
|
|
|
56310
56921
|
// src/mcp/handlers.ts
|
|
56311
56922
|
import { existsSync as existsSync29, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
|
|
56312
|
-
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";
|
|
56313
56924
|
|
|
56314
56925
|
// src/project-lock.ts
|
|
56315
56926
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -56357,7 +56968,7 @@ function positive(v, key) {
|
|
|
56357
56968
|
function requiredCwd(args2, defaults) {
|
|
56358
56969
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
56359
56970
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
56360
|
-
const abs =
|
|
56971
|
+
const abs = resolve11(cwd);
|
|
56361
56972
|
if (!existsSync29(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
56362
56973
|
if (!statSync11(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
56363
56974
|
return abs;
|
|
@@ -56388,6 +56999,8 @@ async function dispatch(name2, args2, cwd) {
|
|
|
56388
56999
|
return handleReport(args2, cwd);
|
|
56389
57000
|
case "ultra11y_prd":
|
|
56390
57001
|
return handlePrd(args2, cwd);
|
|
57002
|
+
case "ultra11y_tickets":
|
|
57003
|
+
return handleTickets(args2, cwd);
|
|
56391
57004
|
case "ultra11y_check":
|
|
56392
57005
|
return handleCheck(args2, cwd);
|
|
56393
57006
|
case "ultra11y_verify":
|
|
@@ -56476,6 +57089,24 @@ function handlePrd(args2, cwd) {
|
|
|
56476
57089
|
...str2(args2.split) === "criterion" ? { grouped_by: "criterion" } : {}
|
|
56477
57090
|
};
|
|
56478
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
|
+
}
|
|
56479
57110
|
function handleCheck(args2, cwd) {
|
|
56480
57111
|
const text = reportText(args2, "check");
|
|
56481
57112
|
const res = checkReport(text, standardOf(args2), langOf(args2));
|
|
@@ -56581,7 +57212,7 @@ function handleInit(args2, cwd) {
|
|
|
56581
57212
|
throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
|
|
56582
57213
|
}
|
|
56583
57214
|
const failOn = failOnRaw;
|
|
56584
|
-
const enginePath =
|
|
57215
|
+
const enginePath = resolve11("scripts/ultra11y.mjs");
|
|
56585
57216
|
const written = [];
|
|
56586
57217
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
56587
57218
|
if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
|
|
@@ -56805,6 +57436,26 @@ var TOOLS2 = [
|
|
|
56805
57436
|
required: ["cwd"]
|
|
56806
57437
|
}
|
|
56807
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
|
+
},
|
|
56808
57459
|
{
|
|
56809
57460
|
name: "ultra11y_criteria",
|
|
56810
57461
|
title: "The offline standards reference",
|
|
@@ -56983,6 +57634,11 @@ var TOOL_META2 = {
|
|
|
56983
57634
|
ultra11y_audit: { openWorld: false },
|
|
56984
57635
|
ultra11y_report: { openWorld: false },
|
|
56985
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 },
|
|
56986
57642
|
ultra11y_criteria: { openWorld: false },
|
|
56987
57643
|
ultra11y_check: { openWorld: false },
|
|
56988
57644
|
ultra11y_verify: { openWorld: false },
|
|
@@ -57151,13 +57807,13 @@ var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
|
57151
57807
|
|
|
57152
57808
|
// src/mcp/resources.ts
|
|
57153
57809
|
import { existsSync as existsSync30, readdirSync as readdirSync6, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
|
|
57154
|
-
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";
|
|
57155
57811
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
57156
57812
|
var SKILL_NAME = "ultra11y";
|
|
57157
57813
|
var URI_SCHEME = "skill://";
|
|
57158
57814
|
function resolveSkillRoot(moduleDir) {
|
|
57159
57815
|
const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
|
|
57160
|
-
const candidates2 = [
|
|
57816
|
+
const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
|
|
57161
57817
|
return candidates2.find((dir) => existsSync30(join44(dir, "SKILL.md")));
|
|
57162
57818
|
}
|
|
57163
57819
|
function listResources(moduleDir) {
|
|
@@ -57180,7 +57836,7 @@ function readResource(uri, moduleDir) {
|
|
|
57180
57836
|
if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
|
|
57181
57837
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
57182
57838
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
57183
|
-
const target =
|
|
57839
|
+
const target = resolve12(root, rel2);
|
|
57184
57840
|
const rootReal = realpathSync5(root);
|
|
57185
57841
|
let targetReal;
|
|
57186
57842
|
try {
|
|
@@ -57461,14 +58117,14 @@ function startHttpServer(opts = {}) {
|
|
|
57461
58117
|
server.requestTimeout = 0;
|
|
57462
58118
|
server.headersTimeout = 6e4;
|
|
57463
58119
|
server.keepAliveTimeout = 12e4;
|
|
57464
|
-
return new Promise((
|
|
58120
|
+
return new Promise((resolve13, reject) => {
|
|
57465
58121
|
server.once("error", reject);
|
|
57466
58122
|
server.listen(opts.port ?? 0, bind, () => {
|
|
57467
58123
|
server.removeListener("error", reject);
|
|
57468
58124
|
const addr2 = server.address();
|
|
57469
58125
|
const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
|
|
57470
58126
|
const host = bind.includes(":") ? `[${bind}]` : bind;
|
|
57471
|
-
|
|
58127
|
+
resolve13({
|
|
57472
58128
|
server,
|
|
57473
58129
|
port,
|
|
57474
58130
|
url: `http://${host}:${port}${MCP_PATH}`,
|
|
@@ -57577,7 +58233,7 @@ function sendJson(res, status, body2, origin, extra = {}) {
|
|
|
57577
58233
|
}
|
|
57578
58234
|
var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
|
|
57579
58235
|
function readBody2(req) {
|
|
57580
|
-
return new Promise((
|
|
58236
|
+
return new Promise((resolve13, reject) => {
|
|
57581
58237
|
const chunks = [];
|
|
57582
58238
|
let size = 0;
|
|
57583
58239
|
let over = false;
|
|
@@ -57601,7 +58257,7 @@ function readBody2(req) {
|
|
|
57601
58257
|
});
|
|
57602
58258
|
req.on("end", () => {
|
|
57603
58259
|
if (over) reject(new Error("too large"));
|
|
57604
|
-
else
|
|
58260
|
+
else resolve13(Buffer.concat(chunks).toString("utf8"));
|
|
57605
58261
|
});
|
|
57606
58262
|
req.on("error", reject);
|
|
57607
58263
|
req.on("aborted", () => reject(new Error("client aborted the request")));
|
|
@@ -57621,8 +58277,11 @@ Usage:
|
|
|
57621
58277
|
ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
|
|
57622
58278
|
ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] (rendered-DOM captures + .ultra11y/pages snapshots: audit real HTML)
|
|
57623
58279
|
ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
|
|
58280
|
+
ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
|
|
57624
58281
|
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
57625
|
-
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--
|
|
58282
|
+
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
|
|
58283
|
+
ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
|
|
58284
|
+
ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
|
|
57626
58285
|
ultra11y render [<dir>] [--scaffold | --setup | --e2e | --coverage | --storybook] [--runner playwright|cypress|auto] [--captures <dir>] [--out <file>] [--json] [--lang auto|en|fr]
|
|
57627
58286
|
ultra11y criteria [<sc>] [--list] [--standard <pack> [--theme <N>]] [--generate] [--json] [--lang auto|en|fr]
|
|
57628
58287
|
ultra11y criteria --standard <pack> --glossary [<term>] (the terms the standard DEFINES \u2014 its tests depend on them)
|
|
@@ -57678,9 +58337,14 @@ Commands:
|
|
|
57678
58337
|
active standard's vocabulary (RGAA "Th\xE9matique/Crit\xE8re/Test", WCAG core
|
|
57679
58338
|
"Principle\xB7Guideline/Success criterion/Technique") \u2014 theme, criterion +
|
|
57680
58339
|
official wording, test(s), WCAG mapping + level, finding, expected state,
|
|
57681
|
-
verification. --split criterion writes one file per criterion
|
|
57682
|
-
|
|
57683
|
-
|
|
58340
|
+
verification. --split criterion writes one file per criterion.
|
|
58341
|
+
It writes MARKDOWN only \u2014 filing tickets is the 'tickets' command.
|
|
58342
|
+
tickets File the audit as TRACKER TICKETS \u2014 GitHub, GitLab or Jira. Reads an
|
|
58343
|
+
audit.json and pushes; it writes no markdown, exactly as prd/report
|
|
58344
|
+
write markdown and push nothing. --grain chooses what ONE ticket is:
|
|
58345
|
+
per criterion (default), per page, per page+criterion, per file, or one
|
|
58346
|
+
consolidated. De-dupe is by exact title, so re-running never duplicates.
|
|
58347
|
+
--dry-run prints the plan without creating anything.
|
|
57684
58348
|
render Get RENDERED HTML to audit (so component libraries like DSFR are
|
|
57685
58349
|
checked as the HTML they emit, not their JSX sources): detect the
|
|
57686
58350
|
framework and print the build\u2192audit recipe, or --scaffold a
|
|
@@ -57848,11 +58512,14 @@ Options:
|
|
|
57848
58512
|
plus <out>/index.md (recommended past 3\u20134 pages: RGAA is 106 criteria)
|
|
57849
58513
|
--no-technical prd (audit format): omit the technical ticket sections (Partie
|
|
57850
58514
|
technique + Contexte de reproduction) for a pure-auditor block
|
|
57851
|
-
--
|
|
57852
|
-
|
|
57853
|
-
--
|
|
57854
|
-
|
|
57855
|
-
|
|
58515
|
+
--provider <id> tickets: auto|github|gitlab|jira. 'auto' reads ULTRA11Y_TICKET_PROVIDER,
|
|
58516
|
+
then .ultra11yrc.json, then the git remote (Jira is never auto-detected)
|
|
58517
|
+
--grain <mode> tickets: what one ticket is \u2014 criterion (default) | page | page-criterion | single | file
|
|
58518
|
+
--transport <t> tickets: auto|cli|rest (mcp: stdio|http). 'auto' prefers the CLI (gh/glab),
|
|
58519
|
+
falling back to REST when only a token is available. Jira is REST-only
|
|
58520
|
+
--max-tickets <n> tickets: refuse to file more than n tickets in one run (default 200)
|
|
58521
|
+
tickets --out <dir> also writes the tracker-agnostic set to
|
|
58522
|
+
<dir>/issues-<date>.json, for a workflow engine to file itself
|
|
57856
58523
|
--scaffold render: write an SSR-snapshot harness (default: ultra11y-render.tsx)
|
|
57857
58524
|
--setup render: install the zero-touch test-render capture harvester (.ultra11y/capture-setup.mjs) + print the runner wiring
|
|
57858
58525
|
--coverage render: report rendered-capture coverage (covered vs blind-spot components); with --json emits the coverage object
|
|
@@ -57961,6 +58628,7 @@ var COMMANDS = [
|
|
|
57961
58628
|
"audit",
|
|
57962
58629
|
"report",
|
|
57963
58630
|
"prd",
|
|
58631
|
+
"tickets",
|
|
57964
58632
|
"render",
|
|
57965
58633
|
"criteria",
|
|
57966
58634
|
"check",
|
|
@@ -57986,6 +58654,9 @@ function isCommand(s) {
|
|
|
57986
58654
|
}
|
|
57987
58655
|
var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
57988
58656
|
"out",
|
|
58657
|
+
"provider",
|
|
58658
|
+
"grain",
|
|
58659
|
+
"max-tickets",
|
|
57989
58660
|
"in",
|
|
57990
58661
|
"include",
|
|
57991
58662
|
"exclude",
|
|
@@ -58067,9 +58738,6 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
58067
58738
|
"semantic",
|
|
58068
58739
|
"manual",
|
|
58069
58740
|
"no-technical",
|
|
58070
|
-
"gh-issues",
|
|
58071
|
-
"gh-single",
|
|
58072
|
-
"issues-json",
|
|
58073
58741
|
"override",
|
|
58074
58742
|
"local",
|
|
58075
58743
|
"docker",
|
|
@@ -58202,7 +58870,73 @@ function emitCiFormat(result, format, standard, lang, failOn) {
|
|
|
58202
58870
|
);
|
|
58203
58871
|
}
|
|
58204
58872
|
}
|
|
58873
|
+
async function cmdAuditFromFile(p, inPath) {
|
|
58874
|
+
const scoping = [
|
|
58875
|
+
"since",
|
|
58876
|
+
"changed",
|
|
58877
|
+
"staged",
|
|
58878
|
+
"baseline",
|
|
58879
|
+
"graph",
|
|
58880
|
+
"cross-file",
|
|
58881
|
+
"jsx",
|
|
58882
|
+
"include",
|
|
58883
|
+
"exclude",
|
|
58884
|
+
"ext",
|
|
58885
|
+
"max-files",
|
|
58886
|
+
"dedup",
|
|
58887
|
+
"captures",
|
|
58888
|
+
"no-captures",
|
|
58889
|
+
"require-captures",
|
|
58890
|
+
"no-default-excludes",
|
|
58891
|
+
"out"
|
|
58892
|
+
];
|
|
58893
|
+
const offenders = scoping.filter((f) => p.flags[f] !== void 0);
|
|
58894
|
+
if (p.positionals.length) offenders.unshift("<paths>");
|
|
58895
|
+
if (offenders.length) {
|
|
58896
|
+
console.error(
|
|
58897
|
+
`ultra11y audit: --in re-gates an audit that already exists, so these have nothing left to change: ${offenders.join(", ")}. Drop them, or run a fresh audit without --in.`
|
|
58898
|
+
);
|
|
58899
|
+
return 2;
|
|
58900
|
+
}
|
|
58901
|
+
let result;
|
|
58902
|
+
try {
|
|
58903
|
+
const parsed = JSON.parse(inPath === "-" ? await readStdin() : readText(inPath));
|
|
58904
|
+
if (!isCurrentAudit(parsed)) {
|
|
58905
|
+
console.error("ultra11y audit: --in is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
|
|
58906
|
+
return 2;
|
|
58907
|
+
}
|
|
58908
|
+
result = parsed;
|
|
58909
|
+
} catch {
|
|
58910
|
+
console.error(`ultra11y audit: --in file not found or not valid JSON: ${inPath}.`);
|
|
58911
|
+
return 2;
|
|
58912
|
+
}
|
|
58913
|
+
const lang = resolveLang(p.flags, { audit: result });
|
|
58914
|
+
const failOnRaw = p.flags["fail-on"];
|
|
58915
|
+
const failOnParsed = parseFailOn(failOnRaw);
|
|
58916
|
+
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
58917
|
+
console.error(`ultra11y audit: --fail-on must be blocking|major|minor (got "${String(failOnRaw)}").`);
|
|
58918
|
+
return 2;
|
|
58919
|
+
}
|
|
58920
|
+
const ciFormat = parseCiFormat(p.flags.format);
|
|
58921
|
+
if (ciFormat === null) {
|
|
58922
|
+
console.error(`ultra11y audit: --format must be sarif|github (got "${String(p.flags.format)}").`);
|
|
58923
|
+
return 2;
|
|
58924
|
+
}
|
|
58925
|
+
const failOnSet = failOnRaw !== void 0;
|
|
58926
|
+
const failOn = failOnSet ? failOnParsed ?? "bloquant" : void 0;
|
|
58927
|
+
const failing = failOn ? findingsAtOrAbove(result.findings, failOn) : [];
|
|
58928
|
+
if (ciFormat) emitCiFormat(result, ciFormat, CORE2, lang, failOn);
|
|
58929
|
+
else if (p.flags.json) console.log(JSON.stringify(result, null, 2));
|
|
58930
|
+
else {
|
|
58931
|
+
console.log(auditSummary(result, lang));
|
|
58932
|
+
if (failOnSet && failing.length)
|
|
58933
|
+
console.error(lang === "fr" ? `\u2717 ${failing.length} non-conformit\xE9(s) \u2265 ${failOn}.` : `\u2717 ${failing.length} non-conformity(ies) \u2265 ${failOn}.`);
|
|
58934
|
+
}
|
|
58935
|
+
return failing.length ? 1 : 0;
|
|
58936
|
+
}
|
|
58205
58937
|
async function cmdAudit(p) {
|
|
58938
|
+
const inFlag = p.flags.in;
|
|
58939
|
+
if (typeof inFlag === "string" && inFlag) return cmdAuditFromFile(p, inFlag);
|
|
58206
58940
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
58207
58941
|
if (inputs.length === 0) {
|
|
58208
58942
|
console.error("ultra11y audit: provide files/globs, or '-' to read stdin.");
|
|
@@ -58371,9 +59105,9 @@ async function cmdDev(p) {
|
|
|
58371
59105
|
}
|
|
58372
59106
|
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}`);
|
|
58373
59107
|
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.");
|
|
58374
|
-
await new Promise((
|
|
59108
|
+
await new Promise((resolve13) => {
|
|
58375
59109
|
const stop2 = () => {
|
|
58376
|
-
void server.close().then(
|
|
59110
|
+
void server.close().then(resolve13);
|
|
58377
59111
|
};
|
|
58378
59112
|
process.once("SIGINT", stop2);
|
|
58379
59113
|
process.once("SIGTERM", stop2);
|
|
@@ -58843,45 +59577,207 @@ async function cmdPrd(p) {
|
|
|
58843
59577
|
const paths = writePrd(result, { out: out2, lang, split, format, standard, technical });
|
|
58844
59578
|
const json = p.flags.json === true;
|
|
58845
59579
|
if (!json) for (const path of paths) console.log(path);
|
|
58846
|
-
|
|
58847
|
-
|
|
58848
|
-
|
|
58849
|
-
|
|
58850
|
-
|
|
58851
|
-
|
|
58852
|
-
|
|
58853
|
-
|
|
58854
|
-
|
|
58855
|
-
|
|
59580
|
+
if (json) console.log(JSON.stringify({ paths, units: prdUnits(result, standard, lang) }, null, 2));
|
|
59581
|
+
return 0;
|
|
59582
|
+
}
|
|
59583
|
+
var REMOVED_FLAGS = {
|
|
59584
|
+
"gh-issues": "ultra11y tickets --in <audit.json> --provider github --grain criterion",
|
|
59585
|
+
"gh-single": "ultra11y tickets --in <audit.json> --provider github --grain single",
|
|
59586
|
+
// The tracker-agnostic export moved WITH ticket filing: `tickets --out` writes the same
|
|
59587
|
+
// envelope (and a superset of the payload) at any grain, so an orchestrator still reads
|
|
59588
|
+
// one stable path — just not from the command that renders documents.
|
|
59589
|
+
"issues-json": "ultra11y tickets --in <audit.json> --out <dir> --grain criterion"
|
|
59590
|
+
};
|
|
59591
|
+
var SECRET_CONFIG_KEYS = ["token", "apiToken", "api_token", "password", "secret"];
|
|
59592
|
+
async function cmdTickets(p) {
|
|
59593
|
+
const standard = stdOf(p, "tickets");
|
|
59594
|
+
if (standard === null) return 2;
|
|
59595
|
+
const inFlag = p.flags.in;
|
|
59596
|
+
if (typeof inFlag !== "string" || !inFlag) {
|
|
59597
|
+
console.error("ultra11y tickets: --in <audit.json> is required ('-' for stdin). Run `audit --out` first.");
|
|
59598
|
+
return 2;
|
|
59599
|
+
}
|
|
59600
|
+
const raw = inFlag === "-" ? await readStdin() : readInputFile(inFlag, "tickets", "--in");
|
|
59601
|
+
if (raw === null) return 2;
|
|
59602
|
+
let result;
|
|
59603
|
+
try {
|
|
59604
|
+
result = JSON.parse(raw);
|
|
59605
|
+
} catch {
|
|
59606
|
+
console.error("ultra11y tickets: --in is not valid JSON (expected an AuditResult).");
|
|
59607
|
+
return 2;
|
|
59608
|
+
}
|
|
59609
|
+
if (!isCurrentAudit(result)) {
|
|
59610
|
+
console.error("ultra11y tickets: input is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
|
|
59611
|
+
return 2;
|
|
59612
|
+
}
|
|
59613
|
+
const lang = resolveLang(p.flags, { audit: result, standard });
|
|
59614
|
+
const fr = lang === "fr";
|
|
59615
|
+
const json = p.flags.json === true;
|
|
59616
|
+
const dryRun = p.flags["dry-run"] === true;
|
|
59617
|
+
let config;
|
|
59618
|
+
try {
|
|
59619
|
+
config = loadConfig(process.cwd())?.tickets;
|
|
59620
|
+
} catch (e) {
|
|
59621
|
+
console.error(`ultra11y tickets: ${e instanceof Error ? e.message : String(e)}`);
|
|
59622
|
+
return 2;
|
|
59623
|
+
}
|
|
59624
|
+
for (const key of SECRET_CONFIG_KEYS) {
|
|
59625
|
+
if (config && key in config) {
|
|
59626
|
+
console.error(
|
|
59627
|
+
`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.`
|
|
59628
|
+
);
|
|
59629
|
+
return 2;
|
|
59630
|
+
}
|
|
59631
|
+
}
|
|
59632
|
+
const grainFlag = typeof p.flags.grain === "string" ? p.flags.grain : config?.grain ?? "criterion";
|
|
59633
|
+
if (!ALL_GRAINS.includes(grainFlag)) {
|
|
59634
|
+
console.error(`ultra11y tickets: --grain must be one of ${ALL_GRAINS.join("|")} (got "${grainFlag}").`);
|
|
59635
|
+
return 2;
|
|
59636
|
+
}
|
|
59637
|
+
const transportFlag = typeof p.flags.transport === "string" ? p.flags.transport : config?.transport ?? "auto";
|
|
59638
|
+
if (!["auto", "cli", "rest"].includes(transportFlag)) {
|
|
59639
|
+
console.error(`ultra11y tickets: --transport must be auto, cli or rest (got "${transportFlag}").`);
|
|
59640
|
+
return 2;
|
|
59641
|
+
}
|
|
59642
|
+
const maxTickets = Number.parseInt(String(p.flags["max-tickets"] ?? config?.maxTickets ?? 200), 10);
|
|
59643
|
+
if (!Number.isFinite(maxTickets) || maxTickets < 1) {
|
|
59644
|
+
console.error("ultra11y tickets: --max-tickets must be a positive integer.");
|
|
59645
|
+
return 2;
|
|
59646
|
+
}
|
|
59647
|
+
const providerFlag = typeof p.flags.provider === "string" ? p.flags.provider : "auto";
|
|
59648
|
+
const providerId = providerFlag === "auto" ? autoProvider(process.env, config?.provider) : isProviderId(providerFlag) ? providerFlag : void 0;
|
|
59649
|
+
if (!providerId) {
|
|
59650
|
+
console.error(
|
|
59651
|
+
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("|")}.`
|
|
58856
59652
|
);
|
|
58857
|
-
|
|
59653
|
+
return 2;
|
|
58858
59654
|
}
|
|
58859
|
-
const
|
|
58860
|
-
|
|
58861
|
-
|
|
58862
|
-
|
|
58863
|
-
|
|
58864
|
-
|
|
58865
|
-
|
|
58866
|
-
|
|
58867
|
-
|
|
58868
|
-
|
|
58869
|
-
|
|
58870
|
-
|
|
58871
|
-
|
|
58872
|
-
|
|
58873
|
-
|
|
58874
|
-
|
|
58875
|
-
|
|
58876
|
-
|
|
58877
|
-
|
|
58878
|
-
|
|
58879
|
-
|
|
59655
|
+
const provider = createProvider(providerId, { transport: transportFlag });
|
|
59656
|
+
const format = p.flags.format === "remediation" ? "remediation" : "audit";
|
|
59657
|
+
const plan = buildTickets(result, {
|
|
59658
|
+
grain: grainFlag,
|
|
59659
|
+
standard,
|
|
59660
|
+
lang,
|
|
59661
|
+
format,
|
|
59662
|
+
bodyLimit: provider.capabilities.bodyLimit,
|
|
59663
|
+
baseDir: process.cwd(),
|
|
59664
|
+
technical: p.flags["no-technical"] !== true
|
|
59665
|
+
});
|
|
59666
|
+
if (plan.error === "no-pages") {
|
|
59667
|
+
console.error(
|
|
59668
|
+
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."
|
|
59669
|
+
);
|
|
59670
|
+
return 1;
|
|
59671
|
+
}
|
|
59672
|
+
if (plan.tickets.length > maxTickets) {
|
|
59673
|
+
console.error(
|
|
59674
|
+
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.`
|
|
59675
|
+
);
|
|
59676
|
+
return 2;
|
|
59677
|
+
}
|
|
59678
|
+
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
59679
|
+
let setPath;
|
|
59680
|
+
if (ticketsOut) {
|
|
59681
|
+
mkdirSync15(ticketsOut, { recursive: true });
|
|
59682
|
+
setPath = join45(ticketsOut, `issues-${result.date}.json`);
|
|
59683
|
+
const payload = {
|
|
59684
|
+
tool: "ultra11y",
|
|
59685
|
+
kind: "issues",
|
|
59686
|
+
schemaVersion: TICKET_SET_SCHEMA_VERSION,
|
|
59687
|
+
standard,
|
|
59688
|
+
grain: grainFlag,
|
|
59689
|
+
date: result.date,
|
|
59690
|
+
count: plan.tickets.length,
|
|
59691
|
+
issues: plan.tickets
|
|
59692
|
+
};
|
|
59693
|
+
writeFileSync17(setPath, `${JSON.stringify(payload, null, 2)}
|
|
59694
|
+
`);
|
|
59695
|
+
if (!json) console.log(setPath);
|
|
59696
|
+
}
|
|
59697
|
+
if (!plan.tickets.length) {
|
|
59698
|
+
if (!json)
|
|
59699
|
+
console.log(
|
|
59700
|
+
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."
|
|
59701
|
+
);
|
|
59702
|
+
if (json)
|
|
59703
|
+
console.log(
|
|
59704
|
+
JSON.stringify(
|
|
59705
|
+
{
|
|
59706
|
+
provider: providerId,
|
|
59707
|
+
transport: provider.transport,
|
|
59708
|
+
grain: grainFlag,
|
|
59709
|
+
standard,
|
|
59710
|
+
dryRun,
|
|
59711
|
+
...setPath ? { setPath } : {},
|
|
59712
|
+
tickets: [],
|
|
59713
|
+
unattributed: plan.unattributed,
|
|
59714
|
+
result: { created: 0, skipped: 0, failed: 0, createdTitles: [], createdUrls: [], errors: [] }
|
|
59715
|
+
},
|
|
59716
|
+
null,
|
|
59717
|
+
2
|
|
59718
|
+
)
|
|
59719
|
+
);
|
|
59720
|
+
return 0;
|
|
59721
|
+
}
|
|
59722
|
+
if (!provider.available() && !dryRun) {
|
|
59723
|
+
console.error(`ultra11y tickets: ${providerId} is not usable here \u2014 ${provider.unavailableReason()}.`);
|
|
59724
|
+
return 1;
|
|
59725
|
+
}
|
|
59726
|
+
const { plan: planned, result: pushed, dedupeChecked } = await pushTickets(plan.tickets, provider, { dryRun: dryRun || !provider.available() });
|
|
59727
|
+
if (json) {
|
|
59728
|
+
console.log(
|
|
59729
|
+
JSON.stringify(
|
|
59730
|
+
{
|
|
59731
|
+
provider: providerId,
|
|
59732
|
+
transport: provider.transport,
|
|
59733
|
+
grain: grainFlag,
|
|
59734
|
+
standard,
|
|
59735
|
+
dryRun,
|
|
59736
|
+
...setPath ? { setPath } : {},
|
|
59737
|
+
dedupeChecked,
|
|
59738
|
+
tickets: planned.map(({ ticket, action }) => ({
|
|
59739
|
+
title: ticket.title,
|
|
59740
|
+
labels: ticket.labels,
|
|
59741
|
+
severity: ticket.severity,
|
|
59742
|
+
advisory: ticket.advisory,
|
|
59743
|
+
scope: ticket.scope,
|
|
59744
|
+
bodyChars: ticket.body.length,
|
|
59745
|
+
action,
|
|
59746
|
+
// The body rides along ONLY under --dry-run: an agent inspecting the plan wants
|
|
59747
|
+
// it, a 200-ticket push payload does not.
|
|
59748
|
+
...dryRun ? { body: ticket.body } : {}
|
|
59749
|
+
})),
|
|
59750
|
+
unattributed: plan.unattributed,
|
|
59751
|
+
result: pushed
|
|
59752
|
+
},
|
|
59753
|
+
null,
|
|
59754
|
+
2
|
|
59755
|
+
)
|
|
59756
|
+
);
|
|
59757
|
+
} else if (dryRun || !provider.available()) {
|
|
59758
|
+
const create = planned.filter((x) => x.action === "create").length;
|
|
59759
|
+
console.log(
|
|
59760
|
+
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.`
|
|
59761
|
+
);
|
|
59762
|
+
for (const { ticket, action } of planned) console.log(` ${action === "create" ? "+" : "="} ${ticket.title}`);
|
|
59763
|
+
if (!dedupeChecked)
|
|
59764
|
+
console.error(
|
|
59765
|
+
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.'
|
|
59766
|
+
);
|
|
59767
|
+
if (!provider.available()) console.error(`ultra11y tickets: ${providerId} \u2014 ${provider.unavailableReason()}.`);
|
|
59768
|
+
} else {
|
|
59769
|
+
console.log(
|
|
59770
|
+
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` : ""}.`
|
|
59771
|
+
);
|
|
59772
|
+
for (const u of pushed.createdUrls) console.log(` ${u}`);
|
|
59773
|
+
for (const e of pushed.errors) console.error(fr ? `ultra11y tickets : \xE9chec \u2014 ${e}` : `ultra11y tickets: failed \u2014 ${e}`);
|
|
59774
|
+
if (plan.unattributed) {
|
|
59775
|
+
console.error(
|
|
59776
|
+
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.`
|
|
59777
|
+
);
|
|
58880
59778
|
}
|
|
58881
59779
|
}
|
|
58882
|
-
|
|
58883
|
-
console.log(JSON.stringify({ paths, ...issuesPath ? { issuesPath } : {}, units: prdUnits(result, standard, lang), ...gh2 ? { gh: gh2 } : {} }, null, 2));
|
|
58884
|
-
return gh2 && gh2.failed > 0 ? 1 : 0;
|
|
59780
|
+
return pushed.failed > 0 ? 1 : 0;
|
|
58885
59781
|
}
|
|
58886
59782
|
function depsAt(root) {
|
|
58887
59783
|
const pkgPath = join45(root, "package.json");
|
|
@@ -59845,13 +60741,25 @@ async function main(argv) {
|
|
|
59845
60741
|
console.log(HELP2);
|
|
59846
60742
|
return 0;
|
|
59847
60743
|
}
|
|
60744
|
+
for (const f of p.unknown) {
|
|
60745
|
+
const replacement = REMOVED_FLAGS[f];
|
|
60746
|
+
if (replacement) {
|
|
60747
|
+
console.error(`ultra11y: --${f} was removed in v3 \u2014 ticket creation is now its own command. Use: ${replacement}`);
|
|
60748
|
+
return 2;
|
|
60749
|
+
}
|
|
60750
|
+
}
|
|
59848
60751
|
for (const f of p.unknown) console.error(`ultra11y: unknown flag --${f} (ignored). Run \`ultra11y --help\`.`);
|
|
59849
60752
|
const ENUM_FLAGS = {
|
|
59850
60753
|
lang: ["auto", "en", "fr"],
|
|
59851
60754
|
dedup: ["exact", "normalized", "off"],
|
|
59852
60755
|
format: ["audit", "doc", "remediation", "sarif", "github", "grid", "report"],
|
|
59853
60756
|
split: ["criterion", "page"],
|
|
59854
|
-
runtime: ["auto", "local", "docker"]
|
|
60757
|
+
runtime: ["auto", "local", "docker"],
|
|
60758
|
+
provider: ["auto", "github", "gitlab", "jira"],
|
|
60759
|
+
grain: ["criterion", "page", "page-criterion", "single", "file"],
|
|
60760
|
+
// The union over every command that takes it: `mcp` serves stdio|http, `tickets`
|
|
60761
|
+
// picks cli|rest. Listing one command's values made the guard cry wolf on the other.
|
|
60762
|
+
transport: ["stdio", "http", "auto", "cli", "rest"]
|
|
59855
60763
|
};
|
|
59856
60764
|
for (const [flag, allowed] of Object.entries(ENUM_FLAGS)) {
|
|
59857
60765
|
const v = p.flags[flag];
|
|
@@ -59869,6 +60777,8 @@ async function main(argv) {
|
|
|
59869
60777
|
return cmdAudit(p);
|
|
59870
60778
|
case "report":
|
|
59871
60779
|
return cmdReport(p);
|
|
60780
|
+
case "tickets":
|
|
60781
|
+
return cmdTickets(p);
|
|
59872
60782
|
case "prd":
|
|
59873
60783
|
return cmdPrd(p);
|
|
59874
60784
|
case "render":
|
|
@@ -59937,6 +60847,7 @@ if (isInvokedDirectly()) {
|
|
|
59937
60847
|
}
|
|
59938
60848
|
export {
|
|
59939
60849
|
COMMANDS,
|
|
60850
|
+
REMOVED_FLAGS,
|
|
59940
60851
|
main,
|
|
59941
60852
|
parseArgs
|
|
59942
60853
|
};
|