diff --git a/app/components/LicenseDisplay.vue b/app/components/LicenseDisplay.vue
index c83cb46d3..62ade31fa 100644
--- a/app/components/LicenseDisplay.vue
+++ b/app/components/LicenseDisplay.vue
@@ -1,11 +1,29 @@
@@ -32,4 +50,31 @@ const hasAnyValidLicense = computed(() => tokens.value.some(t => t.type === 'lic
aria-hidden="true"
/>
+
+
+ {{ $t('package.versions.license_change_warning') }}
+
+
+
+
+
+
+
+
+ {{ licenseChangeText }}
+
+
+
+
+
diff --git a/app/composables/useLicenseChanges.ts b/app/composables/useLicenseChanges.ts
new file mode 100644
index 000000000..25478a544
--- /dev/null
+++ b/app/composables/useLicenseChanges.ts
@@ -0,0 +1,74 @@
+import type { MaybeRefOrGetter } from 'vue'
+import { toValue } from 'vue'
+
+export interface LicenseChange {
+ from: string
+ to: string
+ version: string
+}
+
+export interface LicenseChangesResult {
+ changes: LicenseChange[]
+}
+
+// Type definitions for npm registry response
+interface NpmRegistryVersion {
+ version: string
+ license?: string
+}
+
+// for registry responses of $fetch function, the type includes the key versions as well as many others too.
+interface NpmRegistryResponse {
+ time: Record
+ versions: Record
+}
+
+/**
+ * Composable to detect license changes across all versions of a package
+ */
+export function useLicenseChanges(packageName: MaybeRefOrGetter) {
+ return useAsyncData(
+ () => `license-changes:${toValue(packageName)}`,
+ async () => {
+ const name = toValue(packageName)
+ if (!name) return { changes: [] }
+
+ // Fetch full package metadata from npm registry
+ const url = `https://registry.npmjs.org/${name}`
+ const data = await $fetch(url)
+
+ const changes: LicenseChange[] = []
+ let prevLicense: string | undefined = undefined
+
+ // `data.versions` is an object with version keys
+ const versions = Object.values(data.versions) as NpmRegistryVersion[]
+
+ // Sort versions ascending to compare chronologically
+ versions.sort((a, b) => {
+ const dateA = new Date(data.time[a.version] as string).getTime()
+ const dateB = new Date(data.time[b.version] as string).getTime()
+
+ // Ascending order (oldest to newest)
+ return dateA - dateB
+ })
+
+ // Detect license changes
+ for (const version of versions) {
+ const license = (version.license as string) ?? 'UNKNOWN'
+ if (prevLicense && license !== prevLicense) {
+ changes.push({
+ from: prevLicense,
+ to: license,
+ version: version.version as string,
+ })
+ }
+ prevLicense = license
+ }
+ return { changes }
+ },
+ {
+ default: () => ({ changes: [] }),
+ watch: [() => toValue(packageName)],
+ },
+ )
+}
diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue
index 4bc4d85bc..6105b7b72 100644
--- a/app/pages/package/[[org]]/[name].vue
+++ b/app/pages/package/[[org]]/[name].vue
@@ -582,7 +582,11 @@ const showSkeleton = shallowRef(false)
{{ $t('package.stats.license') }}
-
+
{{ $t('package.license.none') }}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 97008c630..5685df918 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -432,7 +432,11 @@
"filter_placeholder": "Filter by semver (e.g. ^3.0.0)",
"filter_invalid": "Invalid semver range",
"filter_help": "Semver range filter help",
+ "license_change_help": "License Change Details",
+ "license_change_item": "from {from} to {to} at version {version}",
"filter_tooltip": "Filter versions using a {link}. For example, ^3.0.0 shows all 3.x versions.",
+ "changed_license": "The license was changed: {license_change}",
+ "license_change_warning": "change!",
"filter_tooltip_link": "semver range",
"no_matches": "No versions match this range",
"copy_alt": {
diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json
index 97308423f..30480a008 100644
--- a/i18n/locales/tr-TR.json
+++ b/i18n/locales/tr-TR.json
@@ -386,7 +386,11 @@
"filter_placeholder": "Semver ile filtrele (örn. ^3.0.0)",
"filter_invalid": "Geçersiz semver aralığı",
"filter_help": "Semver aralığı filtresi yardımı",
+ "license_change_help": "Lisans değişikliği yardımı",
+ "license_change_item": "{version} sürümünde {from}'den {to}'ya",
"filter_tooltip": "Sürümleri {link} kullanarak filtreleyin. Örneğin, ^3.0.0 tüm 3.x sürümlerini gösterir.",
+ "changed_license": "Lisans değişikliği gerçekleşti: {license_change}",
+ "license_change_warning": "değişiklik!",
"filter_tooltip_link": "semver aralığı",
"no_matches": "Bu aralığa uygun sürüm yok",
"copy_alt": {
diff --git a/i18n/schema.json b/i18n/schema.json
index 720013d90..10a241b95 100644
--- a/i18n/schema.json
+++ b/i18n/schema.json
@@ -1300,9 +1300,21 @@
"filter_help": {
"type": "string"
},
+ "license_change_help": {
+ "type": "string"
+ },
+ "license_change_item": {
+ "type": "string"
+ },
"filter_tooltip": {
"type": "string"
},
+ "changed_license": {
+ "type": "string"
+ },
+ "license_change_warning": {
+ "type": "string"
+ },
"filter_tooltip_link": {
"type": "string"
},