-
-
Notifications
You must be signed in to change notification settings - Fork 410
feat: add a warning when the package license changes #2188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3a0543f
bfd9550
fa52bff
bd4ebe5
9afe21d
ecbb660
b91aab5
34b84c5
302952a
45e2528
e4fd978
a5b4e09
61432b3
ae2ac2a
18a58a1
ef3d106
0fac1ce
c3b5986
e1a28da
083e17f
b397996
1cd6227
4b2d0b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <script setup lang="ts"> | ||
| import type { LicenseChangeResponse } from '~/composables/useLicenseChanges' | ||
|
|
||
| const props = defineProps<{ | ||
| change: LicenseChangeResponse['change'] | ||
| }>() | ||
| </script> | ||
|
|
||
| <template> | ||
| <div | ||
| v-if="props.change" | ||
| class="border border-amber-600/40 bg-amber-500/10 rounded-lg mt-1 gap-x-1 py-2 px-3" | ||
| :aria-label="$t('package.versions.license_change_help')" | ||
| > | ||
| <p class="text-md text-amber-800 dark:text-amber-400 flex items-center gap-2"> | ||
| <span | ||
| class="i-lucide:alert-triangle w-4 h-4 flex-shrink-0" | ||
| role="img" | ||
| :aria-label="$t('package.versions.license_change_help')" | ||
| /> | ||
| {{ $t('package.versions.license_change_warning') }} | ||
| </p> | ||
| <p class="text-md text-amber-800 dark:text-amber-400 mt-1"> | ||
| {{ | ||
| $t('package.versions.license_change_record', { | ||
| from: props.change?.from, | ||
| to: props.change?.to, | ||
| }) | ||
| }} | ||
| </p> | ||
| </div> | ||
| </template> | ||
|
|
||
| <style scoped></style> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { MaybeRefOrGetter } from 'vue' | ||
| import { toValue } from 'vue' | ||
|
|
||
| export interface LicenseChangeResponse { | ||
| change: { from: string; to: string } | null | ||
| } | ||
|
|
||
| /** | ||
| * Composable to detect license changes across all versions of a package | ||
| */ | ||
| export function useLicenseChanges( | ||
| packageName: MaybeRefOrGetter<string | null | undefined>, | ||
| resolvedVersion: MaybeRefOrGetter<string | null | undefined> = () => undefined, | ||
| ) { | ||
| const name = computed(() => toValue(packageName)) | ||
| if (!name) return { data: null } // Don't fetch if no name | ||
|
|
||
| const version = computed(() => toValue(resolvedVersion) ?? 'latest') | ||
|
|
||
| const url = computed(() => { | ||
| return name.value ? `/api/registry/license-change/${encodeURIComponent(name.value)}` : '' | ||
| }) | ||
|
|
||
| const result = useFetch<LicenseChangeResponse>(url, { | ||
| query: computed(() => ({ version: version.value })), | ||
| watch: [url, version], | ||
| }) | ||
|
|
||
| return result | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,94 @@ | ||||||||||
| interface LicenseChangeRecord { | ||||||||||
| from: string | ||||||||||
| to: string | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface NpmRegistryVersion { | ||||||||||
| version: string | ||||||||||
| license?: string | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface NpmRegistryResponse { | ||||||||||
| time: Record<string, string> | ||||||||||
| versions: Record<string, NpmRegistryVersion> | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export default defineCachedEventHandler( | ||||||||||
| async event => { | ||||||||||
| // 1. Extract the package name from the catch-all parameter | ||||||||||
| const rawPkg = getRouterParam(event, 'pkg') | ||||||||||
| if (!rawPkg) { | ||||||||||
| throw createError({ | ||||||||||
| statusCode: 400, | ||||||||||
| statusMessage: 'Package name is required', | ||||||||||
| }) | ||||||||||
| } | ||||||||||
| const query = getQuery(event) | ||||||||||
| const version = query.version || 'latest' | ||||||||||
|
|
||||||||||
| const packageName = decodeURIComponent(rawPkg).replace(/\/+$/, '').trim() | ||||||||||
43081j marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| try { | ||||||||||
| // 2. Fetch the "Packument" on the server | ||||||||||
| // This stays on the server, so the client never downloads this massive JSON | ||||||||||
| const data = await $fetch<NpmRegistryResponse>(`https://registry.npmjs.org/${packageName}`) | ||||||||||
|
|
||||||||||
| if (!data.versions || !data.time) { | ||||||||||
| throw createError({ | ||||||||||
| statusCode: 404, | ||||||||||
| statusMessage: 'Package metadata not found', | ||||||||||
| }) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // 3. Process the logic (moved from your composable) | ||||||||||
| const versions = Object.values(data.versions) | ||||||||||
|
|
||||||||||
| // Sort versions chronologically using the 'time' object | ||||||||||
| versions.sort((a, b) => { | ||||||||||
| const timeA = new Date(data.time[a.version] as string).getTime() | ||||||||||
| const timeB = new Date(data.time[b.version] as string).getTime() | ||||||||||
| return timeA - timeB | ||||||||||
| }) | ||||||||||
| let change: LicenseChangeRecord | null = null | ||||||||||
|
|
||||||||||
| const currentVersionIndex = | ||||||||||
| version === 'latest' ? versions.length - 1 : versions.findIndex(v => v.version === version) | ||||||||||
|
|
||||||||||
| const previousVersionIndex = currentVersionIndex - 1 | ||||||||||
| const currentLicense = String(versions[currentVersionIndex]?.license || 'UNKNOWN') | ||||||||||
| const previousLicense = String(versions[previousVersionIndex]?.license || 'UNKNOWN') | ||||||||||
|
|
||||||||||
| if (currentLicense !== previousLicense) { | ||||||||||
| change = { | ||||||||||
| from: previousLicense as string, | ||||||||||
| to: currentLicense as string, | ||||||||||
| } | ||||||||||
| } | ||||||||||
| return { change } | ||||||||||
| } catch (error: any) { | ||||||||||
| throw createError({ | ||||||||||
| statusCode: error.statusCode || 500, | ||||||||||
| statusMessage: `Failed to fetch license data: ${error.message}`, | ||||||||||
| }) | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| // 5. Cache Configuration | ||||||||||
| maxAge: 60 * 60, // time in seconds | ||||||||||
| swr: true, | ||||||||||
| getKey: event => { | ||||||||||
| const pkg = getRouterParam(event, 'pkg') ?? '' | ||||||||||
| const query = getQuery(event) | ||||||||||
|
|
||||||||||
| // 1. remove the /'s from the package name | ||||||||||
| const cleanPkg = pkg.replace(/\/+$/, '').trim() | ||||||||||
|
|
||||||||||
| // 2. Get the version (default to 'latest' if not provided) | ||||||||||
| const version = query.version || 'latest' | ||||||||||
|
|
||||||||||
| // 3. Create a unique string including the version | ||||||||||
| // Result: "license-change:v1:lodash:4.17.21" | ||||||||||
| return `license-change:v2:${cleanPkg}:${version}` | ||||||||||
| }, | ||||||||||
|
Comment on lines
+79
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other e.g. npmx.dev/server/api/registry/analysis/[...pkg].get.ts Lines 61 to 64 in 7f1e259
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @ghostdevv |
||||||||||
| }, | ||||||||||
| ) | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, another thing here - it may be worth doing this on the server so we don't have to fetch the packument on the client. I think I linked this before, but it's also had some improvements since then. Let me know if you want some help, or don't understand!
https://github.com/npmx-dev/npmx.dev/blob/064cf97ebc89136a267a0c44d757bfaf69212b04/app/composables/useInstallSizeDiff.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i am not familiar with nuxt (working with next.js, react and vue, mostly) but i will check this example in detail and run my composable on server. If i have some questions i will return you. Thanks for pointing this out to me.