
























@@ -97,7 +97,7 @@ function displayGalleryPath(
9797for (const root of [params.repoRoot, ...(params.extraRoots ?? [])]) {
9898const resolvedRoot = path.resolve(root);
9999if (isInside(resolvedRoot, absolute)) {
100-return toRepoPath(path.relative(resolvedRoot, absolute));
100+return sanitizeGalleryText(toRepoPath(path.relative(resolvedRoot, absolute)), params);
101101}
102102}
103103}
@@ -210,6 +210,40 @@ export async function resolveQaEvidenceArtifactFile(params: {
210210throw evidenceError("Evidence artifact is not declared by this evidence summary.", 403);
211211}
212212213+export async function resolveQaEvidenceArtifactFileByIndex(params: {
214+artifactIndex: number;
215+entryIndex: number;
216+evidencePath: string;
217+repoRoot: string;
218+}): Promise<string> {
219+const repoRoot = await fs.realpath(path.resolve(params.repoRoot));
220+const evidencePath = await resolveQaEvidenceFile({ inputPath: params.evidencePath, repoRoot });
221+if (
222+!Number.isSafeInteger(params.entryIndex) ||
223+params.entryIndex < 0 ||
224+!Number.isSafeInteger(params.artifactIndex) ||
225+params.artifactIndex < 0
226+) {
227+throw evidenceError("Evidence artifact index is invalid.", 400);
228+}
229+const summary = validateQaEvidenceSummaryJson(
230+JSON.parse(await fs.readFile(evidencePath, "utf8")) as unknown,
231+);
232+const artifact = summary.entries[params.entryIndex]?.execution?.artifacts[params.artifactIndex];
233+if (!artifact) {
234+throw evidenceError("Evidence artifact not found.", 404);
235+}
236+const artifactFile = await resolveArtifactFileWithinRoots({
237+artifactPath: artifact.path,
238+evidenceDir: path.dirname(evidencePath),
239+ repoRoot,
240+});
241+if (!artifactFile) {
242+throw evidenceError("Evidence artifact not found.", 404);
243+}
244+return artifactFile;
245+}
246+213247function isExplicitRepoRootArtifactPath(raw: string): boolean {
214248const normalized = raw.split(/[\\/]+/u).join("/");
215249return normalized.startsWith(".artifacts/");
@@ -369,11 +403,24 @@ async function readJsonIfExists(
369403}
370404}
371405372-function artifactHref(evidencePath: string, artifactPath: string) {
373-const params = new URLSearchParams({
374- evidencePath,
375- artifactPath,
376-});
406+function artifactHref(
407+evidencePath: string,
408+artifact:
409+| {
410+artifactPath: string;
411+}
412+| {
413+artifactIndex: number;
414+entryIndex: number;
415+},
416+) {
417+const params = new URLSearchParams({ evidencePath });
418+if ("artifactPath" in artifact) {
419+params.set("artifactPath", artifact.artifactPath);
420+} else {
421+params.set("entryIndex", String(artifact.entryIndex));
422+params.set("artifactIndex", String(artifact.artifactIndex));
423+}
377424return `/api/evidence/artifact?${params.toString()}`;
378425}
379426@@ -392,7 +439,7 @@ async function buildProducerContextFile(params: {
392439}
393440const repoPath = toRepoRelativePath(params.repoRoot, params.filePath);
394441return {
395-href: artifactHref(params.hrefEvidencePath, params.artifactPath),
442+href: artifactHref(params.hrefEvidencePath, { artifactPath: params.artifactPath }),
396443path: repoPath,
397444preview: await readPreview(realFile, params.previewKind)
398445.then((preview) =>
@@ -407,8 +454,10 @@ async function buildProducerContextFile(params: {
407454408455async function buildArtifactView(params: {
409456allowedArtifactFiles: ReadonlySet<string>;
457+artifactIndex: number;
410458artifact: QaEvidenceArtifact;
411459evidenceDir: string;
460+entryIndex: number;
412461extraRoots: readonly string[];
413462hrefEvidencePath: string;
414463repoRoot: string;
@@ -424,7 +473,7 @@ async function buildArtifactView(params: {
424473 ? toRepoRelativePath(params.repoRoot, realFile)
425474 : null;
426475const displayPath =
427-realFileRepoPath ??
476+(realFileRepoPath ? sanitizeGalleryText(realFileRepoPath, params) : null) ??
428477sanitizeGalleryText(params.artifact.path, {
429478extraRoots: params.extraRoots,
430479repoRoot: params.repoRoot,
@@ -443,14 +492,13 @@ async function buildArtifactView(params: {
443492source: sanitizeGalleryText(params.artifact.source, params),
444493};
445494}
446-const hrefArtifactPath =
447-realFileRepoPath && path.isAbsolute(params.artifact.path)
448- ? `${REPO_ROOT_ARTIFACT_PATH_PREFIX}${realFileRepoPath}`
449- : params.artifact.path;
450495return {
451496exists: true,
452497error: null,
453-href: artifactHref(params.hrefEvidencePath, hrefArtifactPath),
498+href: artifactHref(params.hrefEvidencePath, {
499+artifactIndex: params.artifactIndex,
500+entryIndex: params.entryIndex,
501+}),
454502kind: sanitizeGalleryText(params.artifact.kind, params),
455503 mediaKind,
456504path: displayPath,
@@ -820,7 +868,7 @@ export async function buildQaEvidenceGalleryModel(params: {
820868});
821869const limitArtifactView = createConcurrencyLimit(ARTIFACT_VIEW_CONCURRENCY);
822870const entries = await Promise.all(
823-summary.entries.map(async (entry): Promise<QaEvidenceGalleryEntryView> => {
871+summary.entries.map(async (entry, entryIndex): Promise<QaEvidenceGalleryEntryView> => {
824872counts[entry.result.status] += 1;
825873const sanitizeEntryText = (value: string) =>
826874sanitizeGalleryText(value, {
@@ -829,12 +877,14 @@ export async function buildQaEvidenceGalleryModel(params: {
829877});
830878return {
831879artifacts: await Promise.all(
832-(entry.execution?.artifacts ?? []).map((artifact) =>
880+(entry.execution?.artifacts ?? []).map((artifact, artifactIndex) =>
833881limitArtifactView(() =>
834882buildArtifactView({
835883 allowedArtifactFiles,
836884 artifact,
885+ artifactIndex,
837886 evidenceDir,
887+ entryIndex,
838888extraRoots: [requestedRepoRoot],
839889 hrefEvidencePath,
840890 repoRoot,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。