diff --git a/app/components/LicenseDisplay.vue b/app/components/LicenseDisplay.vue
index c83cb46d35..95f87dba5a 100644
--- a/app/components/LicenseDisplay.vue
+++ b/app/components/LicenseDisplay.vue
@@ -1,11 +1,32 @@
@@ -21,7 +42,7 @@ const hasAnyValidLicense = computed(() => tokens.value.some(t => t.type === 'lic
class="link-subtle"
:title="$t('package.license.view_spdx')"
>
- {{ token.value }}
+ {{ token.value }}!!
{{ token.value }}
{{ token.value }}
@@ -32,4 +53,26 @@ const hasAnyValidLicense = computed(() => tokens.value.some(t => t.type === 'lic
aria-hidden="true"
/>
+
+
change!
+
+
+
+
+
+
+
+ {{ licenseChangeText }}
+
+
+
+
+
diff --git a/app/composables/useLicenseChanges.ts b/app/composables/useLicenseChanges.ts
new file mode 100644
index 0000000000..74b0f0deab
--- /dev/null
+++ b/app/composables/useLicenseChanges.ts
@@ -0,0 +1,80 @@
+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 {
+ versions: Record<
+ string,
+ {
+ version: string
+ license?: string
+ }
+ >
+}
+
+/**
+ * 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 parse = (v: string) => v.split('.').map(Number)
+ const [aMajor, aMinor, aPatch] = parse(a.version as string)
+ const [bMajor, bMinor, bPatch] = parse(b.version as string)
+ if (aMajor !== bMajor) return aMajor! - bMajor!
+ if (aMinor !== bMinor) return aMinor! - bMinor!
+ return aPatch! - bPatch!
+ })
+
+ // 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 a83a101243..75a5c5f16d 100644
--- a/app/pages/package/[[org]]/[name].vue
+++ b/app/pages/package/[[org]]/[name].vue
@@ -583,7 +583,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 f54f447012..3605a48203 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -431,7 +431,10 @@
"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}",
"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 c1b0a40b50..2920f2d436 100644
--- a/i18n/locales/tr-TR.json
+++ b/i18n/locales/tr-TR.json
@@ -386,7 +386,10 @@
"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}",
"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 151a19c977..07d7b04b3e 100644
--- a/i18n/schema.json
+++ b/i18n/schema.json
@@ -1297,9 +1297,18 @@
"filter_help": {
"type": "string"
},
+ "license_change_help": {
+ "type": "string"
+ },
+ "license_change_item": {
+ "type": "string"
+ },
"filter_tooltip": {
"type": "string"
},
+ "changed_license": {
+ "type": "string"
+ },
"filter_tooltip_link": {
"type": "string"
},