feat: add provenance to end of README and provenance badge#436
feat: add provenance to end of README and provenance badge#436danielroe merged 48 commits intonpmx-dev:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
|
I need also to fix a hydration error that i didn't see, and I had a few more ideas |
|
Could someone more knowledgeable please check that the way i handled the hydration mismatch is correct? |
Co-authored-by: Robin <robin.kehl@singular-it.de>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
server/api/registry/provenance/[...pkg].get.ts (2)
35-40: Consider removing redundant version check.This version check duplicates lines 22-27. Since
rawVersionis already validated as truthy before schema parsing, this second check appears unnecessary unless the schema transforms the version in a way that could make it empty.♻️ Suggested simplification
const parsed = v.parse(PackageRouteParamsSchema, { packageName: rawPackageName, version: rawVersion, }) - const { packageName, version } = parsed - if (!version) { - throw createError({ - statusCode: 400, - message: 'Version is required for provenance.', - }) - } + const { packageName, version } = parsed as { packageName: string; version: string }
42-49: Silent null for non-existent versions may mask errors.If
versiondoesn't exist in the packument,versionDatawill be undefined, and the endpoint returnsnull. This is indistinguishable from a valid version without provenance. Consider returning a 404 for non-existent versions to help API consumers differentiate between "version exists but has no provenance" and "version doesn't exist".♻️ Optional: distinguish non-existent versions
const packument = await fetchNpmPackage(packageName) const versionData = packument.versions[version] + if (!versionData) { + throw createError({ + statusCode: 404, + message: `Version ${version} not found for package ${packageName}.`, + }) + } const dist = versionData?.dist as NpmVersionDist | undefined const attestationsUrl = dist?.attestations?.url if (!attestationsUrl) { return null }app/components/PackageProvenanceSection.vue (1)
31-36: Use<i18n-t>to avoidv-htmlas a best practice.The suggested change to use
<i18n-t>with a named slot is technically sound and aligns with best practices to avoidv-htmlwhere possible. However, note thatproviderLabelis not untrusted—it is always a hardcoded safe string ('GitHub Actions', 'GitLab CI', 'CI', or 'Unknown') derived from a known provider mapping, not directly from attestation metadata.Suggested change
- <span - v-html=" - $t('package.provenance_section.built_and_signed_on', { - provider: `<b>${details.providerLabel}</b>`, - }) - " - /> + <i18n-t keypath="package.provenance_section.built_and_signed_on" tag="span"> + <template `#provider`> + <strong>{{ details.providerLabel }}</strong> + </template> + </i18n-t>
| <template v-if="hasProvenance(displayVersion) && provenanceBadgeMounted"> | ||
| <TooltipApp | ||
| :text=" | ||
| provenanceData && provenanceStatus !== 'pending' | ||
| ? $t('package.provenance_section.built_and_signed_on', { | ||
| provider: provenanceData.providerLabel, | ||
| }) | ||
| : $t('package.verified_provenance') | ||
| " | ||
| position="bottom" | ||
| > | ||
| <a | ||
| href="#provenance" | ||
| :aria-label="$t('package.provenance_section.view_more_details')" | ||
| 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" | ||
| > | ||
| <span | ||
| class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" | ||
| aria-hidden="true" | ||
| /> | ||
| </a> | ||
| </TooltipApp> | ||
| </template> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -n 600 app/pages/package/[...package].vue | tail -n 100Repository: npmx-dev/npmx.dev
Length of output: 4689
🏁 Script executed:
wc -l app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 100
🏁 Script executed:
rg "provenanceStatus|provenanceData" app/pages/package/[...package].vue -A 2 -B 2Repository: npmx-dev/npmx.dev
Length of output: 868
🏁 Script executed:
rg "fetchProvenance|useLazyFetch" app/pages/package/[...package].vue -A 5 -B 2Repository: npmx-dev/npmx.dev
Length of output: 1703
🏁 Script executed:
rg "catch|error" app/pages/package/[...package].vue | grep -i provenanceRepository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
web_search
Nuxt useLazyFetch status states documentationRepository: npmx-dev/npmx.dev
Length of output: 154
🌐 Web query:
Nuxt useLazyFetch status possible values states
💡 Result:
In Nuxt, useLazyFetch() returns an AsyncData object whose status is a Ref with these possible string values:
"idle": request hasn’t started yet (common with lazy/non-immediate behavior, or whenserver: falseduring SSR). [1]"pending": request is currently in progress. [1]"success": request finished successfully. [1]"error": request failed. [1]
(These are the same status values used by useFetch / useAsyncData.) [2]
Sources: [1] Nuxt useFetch docs (status values) https://nuxt.com/docs/4.x/api/composables/use-fetch, [2] Nuxt Data Fetching guide https://nuxt.com/docs/4.x/getting-started/data-fetching
🏁 Script executed:
rg "provenanceData\s*=" app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
rg "provenanceData" app/pages/package/[...package].vue | head -20Repository: npmx-dev/npmx.dev
Length of output: 292
🏁 Script executed:
sed -n '200,250p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 1688
🏁 Script executed:
rg -B 20 "status: provenanceStatus" app/pages/package/[...package].vue | head -40Repository: npmx-dev/npmx.dev
Length of output: 644
🏁 Script executed:
rg "provenanceData|fetchProvenance" app/pages/package/[...package].vue -A 10 | grep -A 10 -B 5 "error\|catch\|reset"Repository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
sed -n '290,330p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 1191
🏁 Script executed:
rg -n "const {" app/pages/package/[...package].vue | grep -i provenanceRepository: npmx-dev/npmx.dev
Length of output: 138
🏁 Script executed:
sed -n '320,360p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 1484
🏁 Script executed:
rg -n "useLazyFetch.*Provenance\|data: provenanceData" app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
sed -n '360,400p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 1306
🏁 Script executed:
grep -n "provenanceData\|provenanceStatus\|fetchProvenance" app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 461
🏁 Script executed:
sed -n '160,185p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 596
🏁 Script executed:
sed -n '555,570p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 851
🏁 Script executed:
sed -n '1130,1150p' app/pages/package/[...package].vueRepository: npmx-dev/npmx.dev
Length of output: 766
Use explicit success status to avoid showing stale provider labels after failed fetches.
The current condition provenanceData && provenanceStatus !== 'pending' will display a stale provider label if a provenance fetch fails. When provenanceStatus becomes 'error', the data from a previous successful fetch will still exist and the tooltip will show it. Change the condition to explicitly check for success:
- provenanceData && provenanceStatus !== 'pending'
+ provenanceStatus === 'success' && provenanceData📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <template v-if="hasProvenance(displayVersion) && provenanceBadgeMounted"> | |
| <TooltipApp | |
| :text=" | |
| provenanceData && provenanceStatus !== 'pending' | |
| ? $t('package.provenance_section.built_and_signed_on', { | |
| provider: provenanceData.providerLabel, | |
| }) | |
| : $t('package.verified_provenance') | |
| " | |
| position="bottom" | |
| > | |
| <a | |
| href="#provenance" | |
| :aria-label="$t('package.provenance_section.view_more_details')" | |
| 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" | |
| > | |
| <span | |
| class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" | |
| aria-hidden="true" | |
| /> | |
| </a> | |
| </TooltipApp> | |
| </template> | |
| <template v-if="hasProvenance(displayVersion) && provenanceBadgeMounted"> | |
| <TooltipApp | |
| :text=" | |
| provenanceStatus === 'success' && provenanceData | |
| ? $t('package.provenance_section.built_and_signed_on', { | |
| provider: provenanceData.providerLabel, | |
| }) | |
| : $t('package.verified_provenance') | |
| " | |
| position="bottom" | |
| > | |
| <a | |
| href="#provenance" | |
| :aria-label="$t('package.provenance_section.view_more_details')" | |
| 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" | |
| > | |
| <span | |
| class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" | |
| aria-hidden="true" | |
| /> | |
| </a> | |
| </TooltipApp> | |
| </template> |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
server/utils/provenance.ts (1)
117-131: Prefer selecting attestation entries by content instead of fixed index.Lines 117-131 assume the first resolved dependency/tlog entry is the one you want. If ordering changes, you may silently lose the commit or log index. A
find()on the relevant fields is more robust.♻️ Suggested refactor
- const resolved = pred.buildDefinition?.resolvedDependencies?.[0] - const commitSha = resolved?.digest?.gitCommit ?? '' + const resolved = pred.buildDefinition?.resolvedDependencies?.find(dep => dep.digest?.gitCommit) + const commitSha = resolved?.digest?.gitCommit ?? '' const tlogEntries = slsaAttestation.bundle.verificationMaterial?.tlogEntries - const logIndex = tlogEntries?.[0]?.logIndex + const logIndex = tlogEntries?.find(entry => entry.logIndex)?.logIndexAs per coding guidelines, ensure you always check when accessing an array value by index.
app/pages/package/[...package].vue (1)
162-187: Simplify refetch logic—useLazyFetch with a getter URL auto-refetches on changes.Nuxt 4's
useLazyFetch()automatically refetches when the URL expression is reactive (via a getter function or computed). Here, the URL is already a getter that depends ondisplayVersion, so Nuxt will refetch wheneverdisplayVersionchanges—even withimmediate: false.The manual
watchondisplayVersionwith theprovenanceStatus === 'idle'guard appears redundant and may cause duplicate fetch attempts. Either:
- Remove the manual watch and rely on Nuxt's auto-refetch, or
- If explicit control is needed, document why and consider disabling auto-watch with
watch: falsein the fetch options.

Hello, i created the following:
PackageProvenanceSectionandAppPopovercomponents to display the badge in a popover and a section under the readmebuilder.id) but can ofc be added.Popover:

Clicking "view more details" will link go to the #provenance section under the readme.
Section at the end of the README

This is my first time so let me know if there is anything i need to do or change please.
Feedback is welcome :)
References #244