Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3a0543f
1826-add a warning and info for license changes. Added support for TR…
aisiklar Mar 21, 2026
bfd9550
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 21, 2026
fa52bff
fix:warning style changed and unintended chars erased
aisiklar Mar 24, 2026
bd4ebe5
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 24, 2026
9afe21d
fix:added TR language support for warning text and changed logic for …
aisiklar Mar 24, 2026
ecbb660
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 24, 2026
b91aab5
fix: removed the vue-118n import and used autoimported
aisiklar Mar 24, 2026
34b84c5
Merge branch 'main' into feature/1826_license_changes
ghostdevv Mar 25, 2026
302952a
fix:feature/1826-warning message moved to proper place, license chang…
aisiklar Apr 2, 2026
45e2528
test added
aisiklar Apr 3, 2026
e4fd978
removed the unneeded watcher
aisiklar Apr 3, 2026
a5b4e09
Merge remote-tracking branch 'origin/main' into feature/1826_license_…
ghostdevv Apr 4, 2026
61432b3
moved license change logic to server side, to an api, changed nested …
aisiklar Apr 9, 2026
ae2ac2a
changes to translations and schema from prev merge
aisiklar Apr 9, 2026
18a58a1
chore: update i18n
ghostdevv Apr 9, 2026
ef3d106
Merge remote-tracking branch 'origin/main' into feature/1826_license_…
ghostdevv Apr 9, 2026
0fac1ce
fix(i18n): gen schema
ghostdevv Apr 9, 2026
c3b5986
chore: fmt
ghostdevv Apr 9, 2026
e1a28da
Update server/api/registry/license-change/[...pkg].get.ts
aisiklar Apr 10, 2026
083e17f
fix: compute license changes in page
43081j Apr 10, 2026
b397996
Update i18n/locales/en.json
aisiklar Apr 13, 2026
1cd6227
Update i18n/locales/en.json
aisiklar Apr 13, 2026
4b2d0b9
TR translation added, with suggested changes on the warning message
aisiklar Apr 13, 2026
db9c4ed
custom key added to useFetch function in useLicenseChange, comments u…
aisiklar Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions app/components/LicenseChangeWarning.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script setup lang="ts">
import { useLicenseChanges } from '~/composables/useLicenseChanges'

const props = defineProps<{
license?: string
packageName?: string
resolvedVersion: string | null | undefined
}>()

const licenseChanges = useLicenseChanges(
() => props.packageName,
() => props.resolvedVersion,
)

const changes = computed(() => licenseChanges.data.value?.changes ?? [])

const licenseChangeText = computed(() =>
changes.value
.map(item =>
$t('package.versions.license_change_item', {
from: item.from,
to: item.to,
version: item.version,
}),
)
.join('; '),
)
</script>

<template>
<div
v-if="changes && changes.length > 0"
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">
<i18n-t keypath="package.versions.changed_license" tag="span">
<template #license_change>{{ licenseChangeText }}</template>
</i18n-t>
</p>
</div>
</template>

<style scoped></style>
88 changes: 88 additions & 0 deletions app/composables/useLicenseChanges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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<string, string>
versions: Record<string, NpmRegistryVersion>
}

/**
* Composable to detect license changes across all versions of a package
*/
export function useLicenseChanges(
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Author

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.

packageName: MaybeRefOrGetter<string | null | undefined>,
resolvedVersion: MaybeRefOrGetter<string | null | undefined> = () => undefined,
) {
return useAsyncData<LicenseChangesResult>(
() => {
const name = toValue(packageName)
const version = toValue(resolvedVersion) ?? 'latest'
return `license-changes:${name}:${version}`
},
async () => {
const name = toValue(packageName)
const resolvedVer = toValue(resolvedVersion)
if (!name) return { changes: [] }

// Fetch full package metadata from npm registry
const url = `https://registry.npmjs.org/${name}`
const data = await $fetch<NpmRegistryResponse>(url)

const changes: LicenseChange[] = []

// `data.versions` is an object with version keys
const versions = Object.values(data.versions) as NpmRegistryVersion[]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only compare to the last version, there is some logic here that could be reused (you can move it into its own utility file in shared too

function getComparisonVersion(pkg: SlimPackument, resolvedVersion: string): string | null {
const isCurrentPrerelease = prerelease(resolvedVersion) !== null
if (isCurrentPrerelease) {
const latest = pkg['dist-tags']?.latest
if (!latest || latest === resolvedVersion) return null
return latest
}
// Find the previous version in time that was stable
const stableVersions = Object.keys(pkg.time)
.filter(v => v !== 'modified' && v !== 'created' && valid(v) !== null && prerelease(v) === null)
.sort((a, b) => compare(a, b))
const currentIdx = stableVersions.indexOf(resolvedVersion)
// Don't compare the second version against the first as the first
// has no baseline so a large size difference is expected
if (currentIdx <= 1) return null
return stableVersions[currentIdx - 1]!
}


// 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
})

// When resolvedVer is not provided, check changes across all versions
const targetVersion = resolvedVer ?? versions[versions.length - 1]?.version

if (targetVersion) {
const resolvedIndex = versions.findIndex(v => v.version === targetVersion)

if (resolvedIndex > 0) {
const currentLicense = (versions[resolvedIndex]?.license as string) ?? 'UNKNOWN'
const previousLicense = (versions[resolvedIndex - 1]?.license as string) ?? 'UNKNOWN'

if (currentLicense !== previousLicense) {
changes.push({
from: previousLicense,
to: currentLicense,
version: (versions[resolvedIndex]?.version as string) || 'UNKNOWN',
})
}
}
}
return { changes }
},
{
default: () => ({ changes: [] }),
watch: [() => toValue(packageName), () => toValue(resolvedVersion)],
},
)
}
11 changes: 11 additions & 0 deletions app/pages/package/[[org]]/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,15 @@
() => `/package/${pkg.value?.name || packageName.value}/v/{version}`,
)

//delete
watch(
resolvedVersion,
newV => {
console.log('#1 resolvedVersion ', newV)

Check warning on line 466 in app/pages/package/[[org]]/[name].vue

View workflow job for this annotation

GitHub Actions / 🤖 Autofix code

eslint(no-console)

Unexpected console statement.
},
{ immediate: true },
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const dependencyCount = computed(() => getDependencyCount(displayVersion.value))

const numberFormatter = useNumberFormatter()
Expand Down Expand Up @@ -883,6 +892,8 @@
</section>

<div class="space-y-6" :class="$style.areaVulns">
<!-- license change warning -->
<LicenseChangeWarning :packageName="pkg.name" :resolvedVersion="resolvedVersion" />
Comment thread
ghostdevv marked this conversation as resolved.
Outdated
<!-- Bad package warning -->
<PackageReplacement v-if="moduleReplacement" :replacement="moduleReplacement" />
<!-- Size / dependency increase notice -->
Expand Down
4 changes: 4 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it's nesting the keys right? I think they should be one key, so it'd be like The license was changed {from} to {to}. I also don't think we need the {version} since it's on the version page, wdyt?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree on that. thanks, as we changed the logic from checking all versions to last version, there is really no meaning to show it anymore, even if it runs the logic on resolvedVersion, which user can change by selecting from version selector.
I will check the keys, so that it will be simpler.

"license_change_warning": "License change!",
Comment thread
aisiklar marked this conversation as resolved.
Outdated
"filter_tooltip_link": "semver range",
"no_matches": "No versions match this range",
"copy_alt": {
Expand Down
4 changes: 4 additions & 0 deletions i18n/locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "Lisans değişikliği!",
"filter_tooltip_link": "semver aralığı",
"no_matches": "Bu aralığa uygun sürüm yok",
"copy_alt": {
Expand Down
12 changes: 12 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
Loading