|
1 | 1 | <script setup lang="ts"> |
2 | | -import type { NpmVersionDist, PackumentVersion, ReadmeResponse } from '#shared/types' |
| 2 | +import type { |
| 3 | + NpmVersionDist, |
| 4 | + PackumentVersion, |
| 5 | + ProvenanceDetails, |
| 6 | + ReadmeResponse, |
| 7 | +} from '#shared/types' |
3 | 8 | import type { JsrPackageInfo } from '#shared/types/jsr' |
4 | 9 | import { assertValidPackageName } from '#shared/utils/npm' |
5 | 10 | import { onKeyStroke } from '@vueuse/core' |
@@ -108,6 +113,33 @@ watch( |
108 | 113 | }, |
109 | 114 | ) |
110 | 115 |
|
| 116 | +// Fetch provenance details when the displayed version has attestations |
| 117 | +const { |
| 118 | + data: provenanceData, |
| 119 | + status: provenanceStatus, |
| 120 | + execute: fetchProvenance, |
| 121 | +} = useLazyFetch<ProvenanceDetails | null>( |
| 122 | + () => { |
| 123 | + const v = displayVersion.value |
| 124 | + if (!v || !hasProvenance(v)) return '' |
| 125 | + return `/api/registry/provenance/${packageName.value}/v/${v.version}` |
| 126 | + }, |
| 127 | + { |
| 128 | + default: () => null, |
| 129 | + server: false, |
| 130 | + immediate: false, |
| 131 | + }, |
| 132 | +) |
| 133 | +watch( |
| 134 | + () => [displayVersion.value?.version, hasProvenance(displayVersion.value)], |
| 135 | + () => { |
| 136 | + if (displayVersion.value && hasProvenance(displayVersion.value)) { |
| 137 | + fetchProvenance() |
| 138 | + } |
| 139 | + }, |
| 140 | + { immediate: true }, |
| 141 | +) |
| 142 | +
|
111 | 143 | // Keep latestVersion for comparison (to show "(latest)" badge) |
112 | 144 | const latestVersion = computed(() => { |
113 | 145 | if (!pkg.value) return null |
@@ -408,19 +440,40 @@ function handleClick(event: MouseEvent) { |
408 | 440 | > |
409 | 441 | <span v-else>v{{ displayVersion.version }}</span> |
410 | 442 |
|
411 | | - <a |
412 | | - v-if="hasProvenance(displayVersion)" |
413 | | - :href="`https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`" |
414 | | - target="_blank" |
415 | | - rel="noopener noreferrer" |
416 | | - class="inline-flex items-center justify-center gap-1.5 text-fg-muted hover:text-fg transition-colors duration-200 min-w-6 min-h-6" |
417 | | - :title="$t('package.verified_provenance')" |
418 | | - > |
419 | | - <span |
420 | | - class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" |
421 | | - aria-hidden="true" |
422 | | - /> |
423 | | - </a> |
| 443 | + <AppPopover v-if="hasProvenance(displayVersion)" position="bottom"> |
| 444 | + <template #content> |
| 445 | + <p class="flex items-center gap-2 text-fg m-0"> |
| 446 | + <span |
| 447 | + class="i-solar-shield-check-outline w-3.5 h-3.5 shrink-0 text-emerald-500" |
| 448 | + aria-hidden="true" |
| 449 | + /> |
| 450 | + <span>{{ |
| 451 | + provenanceData |
| 452 | + ? $t('package.provenance_section.built_and_signed_on', { |
| 453 | + provider: provenanceData.providerLabel, |
| 454 | + }) |
| 455 | + : $t('package.verified_provenance') |
| 456 | + }}</span> |
| 457 | + </p> |
| 458 | + <a href="#provenance" class="block mt-1.5 link font-medium"> |
| 459 | + {{ $t('package.provenance_section.view_more_details') }} |
| 460 | + </a> |
| 461 | + </template> |
| 462 | + <template #default="{ popoverVisible }"> |
| 463 | + <a |
| 464 | + :href="`https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`" |
| 465 | + target="_blank" |
| 466 | + rel="noopener noreferrer" |
| 467 | + class="inline-flex items-center justify-center gap-1.5 text-fg-muted hover:text-emerald-500 transition-colors duration-200 min-w-6 min-h-6" |
| 468 | + :class="popoverVisible && 'text-emerald-500'" |
| 469 | + > |
| 470 | + <span |
| 471 | + class="i-solar-shield-check-outline w-3.5 h-3.5 shrink-0" |
| 472 | + aria-hidden="true" |
| 473 | + /> |
| 474 | + </a> |
| 475 | + </template> |
| 476 | + </AppPopover> |
424 | 477 | <span |
425 | 478 | v-if=" |
426 | 479 | requestedVersion && |
@@ -897,6 +950,27 @@ function handleClick(event: MouseEvent) { |
897 | 950 | $t('package.readme.view_on_github') |
898 | 951 | }}</a> |
899 | 952 | </p> |
| 953 | + |
| 954 | + <!-- Provenance details (when version has attestations) --> |
| 955 | + <template v-if="hasProvenance(displayVersion)"> |
| 956 | + <div |
| 957 | + v-if="provenanceStatus === 'pending'" |
| 958 | + class="mt-8 flex items-center gap-2 text-fg-subtle text-sm" |
| 959 | + > |
| 960 | + <span |
| 961 | + class="i-carbon-circle-dash w-4 h-4 motion-safe:animate-spin" |
| 962 | + aria-hidden="true" |
| 963 | + /> |
| 964 | + <span>{{ $t('package.provenance_section.title') }}…</span> |
| 965 | + </div> |
| 966 | + <PackageProvenanceSection |
| 967 | + v-else-if="provenanceData" |
| 968 | + :details="provenanceData" |
| 969 | + :package-name="pkg.name" |
| 970 | + :version="displayVersion?.version" |
| 971 | + class="mt-8" |
| 972 | + /> |
| 973 | + </template> |
900 | 974 | </section> |
901 | 975 |
|
902 | 976 | <div class="area-sidebar"> |
|
0 commit comments