Skip to content

Commit 3522abb

Browse files
authored
diff semver if package dependency is in directory, show tip (#2286)
1 parent 7cb0b10 commit 3522abb

8 files changed

Lines changed: 169 additions & 60 deletions

File tree

API.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ Return aggregated statistics about the library dataset, such as total counts of
202202

203203
## GET /api/library
204204

205-
Lookup one or more libraries by npm package name. Endpoint can optionally perform a quick `check` to return existence flag only.
205+
Lookup one or more libraries by npm package name.
206+
Endpoint can optionally perform a quick `check` to return existence flag only, or when `version` is used as `check` value to return the latest version string when package is present in the directory.
206207

207208
- Method: GET
208209
- Path: `/api/library`
@@ -213,6 +214,7 @@ Lookup one or more libraries by npm package name. Endpoint can optionally perfor
213214
### Notes
214215

215216
- If `check` is `true`, the response includes only information about existence for the specified package(s).
217+
- If `check` is `version`, the response includes only information about existence for the specified package(s), returning the latest version if package exists.
216218
- If `check` is `false` or not provided, the response includes full library data.
217219

218220
### Example
@@ -311,6 +313,16 @@ Lookup one or more libraries by npm package name. Endpoint can optionally perfor
311313
}
312314
```
313315

316+
- GET `/api/library?name=react&check=version`
317+
318+
Response:
319+
320+
```json
321+
{
322+
"uniwind": "1.5.0"
323+
}
324+
```
325+
314326
---
315327

316328
## GET /api/proxy/npm-stat

bun.lock

Lines changed: 64 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/Package/DependenciesSection.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ type Props = {
1010
title: string;
1111
data?: Record<string, string | PeerDependencyData> | null;
1212
checkExistence?: boolean;
13+
checkVersion?: boolean;
1314
};
1415

15-
export default function DependenciesSection({ title, data, checkExistence }: Props) {
16+
export default function DependenciesSection({ title, data, checkExistence, checkVersion }: Props) {
1617
const noData = !data || Object.keys(data).length === 0;
1718

1819
const { data: checkData } = useSWR(
1920
checkExistence && !noData
20-
? `/api/library?name=${Object.keys(data).join(',')}&check=true`
21+
? `/api/library?name=${Object.keys(data).join(',')}&check=${checkVersion ? 'version' : 'true'}`
2122
: null,
2223
(url: string) => fetch(url).then(res => res.json()),
2324
{
@@ -39,7 +40,7 @@ export default function DependenciesSection({ title, data, checkExistence }: Pro
3940
key={`${title.toLocaleLowerCase()}-${name}`}
4041
name={name}
4142
data={depData}
42-
packageExists={checkData?.[name]}
43+
packageVersion={checkData?.[name]}
4344
/>
4445
))}
4546
</CollapsibleSection>

components/Package/DependencyRow.tsx

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { type ReactNode } from 'react';
22
import { View } from 'react-native';
3+
import semverClean from 'semver/functions/clean';
4+
import semverDiff from 'semver/functions/diff';
5+
import semverGt from 'semver/functions/gt';
36

47
import { A, HoverEffect, Label, P, useLayout } from '~/common/styleguide';
58
import { Info, Logo } from '~/components/Icons';
@@ -10,13 +13,16 @@ import tw from '~/util/tailwind';
1013
type Props = {
1114
name: string;
1215
data: string | PeerDependencyData;
13-
packageExists?: boolean;
16+
packageVersion?: string | boolean;
1417
};
1518

16-
export default function DependencyRow({ name, data, packageExists }: Props) {
19+
export default function DependencyRow({ name, data, packageVersion }: Props) {
1720
const { isSmallScreen } = useLayout();
1821
const isDataString = typeof data === 'string';
19-
const versionLabel = getVersionLabel(isDataString ? data : data.version);
22+
const versionLabel = getVersionLabel(
23+
isDataString ? data : data.version,
24+
typeof packageVersion === 'string' ? packageVersion : undefined
25+
);
2026
const hasLongVersion = typeof versionLabel === 'string' && versionLabel.length > 18;
2127

2228
return (
@@ -27,7 +33,7 @@ export default function DependencyRow({ name, data, packageExists }: Props) {
2733
isSmallScreen && tw`my-px`,
2834
]}>
2935
<span style={tw`flex flex-shrink flex-row items-center gap-x-1.5 overflow-hidden pl-0.5`}>
30-
{packageExists ? (
36+
{packageVersion ? (
3137
<Tooltip
3238
side="left"
3339
trigger={
@@ -81,7 +87,7 @@ export default function DependencyRow({ name, data, packageExists }: Props) {
8187
);
8288
}
8389

84-
function getVersionLabel(version: string): ReactNode {
90+
function getVersionLabel(version: string, latestVersion?: string): ReactNode {
8591
if (version.startsWith('http')) {
8692
return (
8793
<A href={version} style={tw`leading-tight`}>
@@ -95,7 +101,56 @@ function getVersionLabel(version: string): ReactNode {
95101
}
96102
return 'patched';
97103
}
98-
return version;
104+
105+
if (latestVersion) {
106+
const cleanVersion = semverClean(version.replace(/^[~^]/, ''));
107+
108+
if (!cleanVersion || semverGt(cleanVersion, latestVersion)) {
109+
return version;
110+
}
111+
112+
const diff = semverDiff(cleanVersion, latestVersion);
113+
switch (diff) {
114+
case 'patch':
115+
return (
116+
<Tooltip
117+
sideOffset={-3}
118+
trigger={
119+
<span style={tw`cursor-pointer text-[#c99319] dark:text-[#dc9a00]`}>{version}</span>
120+
}>
121+
<Label style={tw`font-light text-white`}>
122+
Patch update available: <span style={tw`font-medium`}>{latestVersion}</span>
123+
</Label>
124+
</Tooltip>
125+
);
126+
case 'minor':
127+
return (
128+
<Tooltip
129+
sideOffset={-3}
130+
trigger={<span style={tw`cursor-pointer text-[#ff5900]`}>{version}</span>}>
131+
<Label style={tw`font-light text-white`}>
132+
Minor update available: <span style={tw`font-medium`}>{latestVersion}</span>
133+
</Label>
134+
</Tooltip>
135+
);
136+
case 'major':
137+
return (
138+
<Tooltip
139+
sideOffset={-3}
140+
trigger={
141+
<span style={tw`cursor-pointer text-[#e70a2f] dark:text-[#eb2d39]`}>{version}</span>
142+
}>
143+
<Label style={tw`font-light text-white`}>
144+
Major update available: <span style={tw`font-medium`}>{latestVersion}</span>
145+
</Label>
146+
</Tooltip>
147+
);
148+
default:
149+
return version;
150+
}
151+
} else {
152+
return version;
153+
}
99154
}
100155

101156
function extractPatchedVersion(entry: string): string | null {

next.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const PACKAGES_TO_OPTIMIZE = [
1616
'react-native-svg',
1717
'react-native-web',
1818
'react-shiki',
19+
'semver',
1920
'shiki/*',
2021
'twrnc',
2122
];

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
"@radix-ui/react-hover-card": "^1.1.15",
2828
"@radix-ui/react-tooltip": "^1.2.8",
2929
"@react-native-picker/picker": "^2.11.4",
30-
"@sentry/react": "^10.43.0",
30+
"@sentry/react": "^10.44.0",
3131
"@visx/gradient": "^3.12.0",
3232
"@visx/responsive": "^3.12.0",
3333
"@visx/xychart": "^3.12.0",
3434
"crypto-js": "^4.2.0",
3535
"es-toolkit": "^1.45.1",
36-
"expo": "55.0.6",
36+
"expo": "55.0.7",
3737
"expo-font": "^55.0.4",
38-
"next": "^16.1.6",
38+
"next": "^16.1.7",
3939
"node-emoji": "^2.2.0",
4040
"postcss": "^8.5.8",
4141
"react": "19.2.4",
@@ -51,18 +51,20 @@
5151
"rehype-sanitize": "^6.0.0",
5252
"remark-emoji": "^5.0.2",
5353
"remark-gfm": "^4.0.1",
54+
"semver": "^7.7.4",
5455
"swr": "^2.4.1",
5556
"tailwindcss": "^3.4.19",
5657
"twrnc": "^4.16.0",
5758
"use-debounce": "^10.1.0"
5859
},
5960
"devDependencies": {
6061
"@expo/next-adapter": "^6.0.0",
61-
"@next/bundle-analyzer": "^16.1.6",
62+
"@next/bundle-analyzer": "^16.1.7",
6263
"@prettier/plugin-oxc": "^0.1.3",
6364
"@types/bun": "^1.3.10",
6465
"@types/crypto-js": "^4.2.2",
6566
"@types/react": "^19.2.14",
67+
"@types/semver": "^7.7.1",
6668
"@vercel/blob": "^0.27.3",
6769
"ajv-cli": "^5.0.0",
6870
"browserslist": "^4.28.1",

pages/api/library/index.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type NextApiRequest, type NextApiResponse } from 'next';
22

33
import data from '~/assets/data.json';
4-
import { type DataAssetType } from '~/types';
4+
import { type DataAssetType, type LibraryType } from '~/types';
55
import { parseQueryParams } from '~/util/queryParams';
66

77
const DATASET = data as DataAssetType;
@@ -10,8 +10,6 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
1010
const { name, check } = parseQueryParams(req.query);
1111

1212
const packageNames = name ? name.toString().toLowerCase().trim().split(',') : undefined;
13-
const checkOnly = Boolean(check);
14-
1513
const libraries = DATASET.libraries.filter(library =>
1614
packageNames?.includes(library.npmPkg.toLowerCase())
1715
);
@@ -29,13 +27,18 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
2927

3028
res.statusCode = 200;
3129
res.json(
32-
Object.fromEntries(
33-
packageNames.map(name => [
34-
name,
35-
checkOnly
36-
? libraries.filter(library => name === library.npmPkg).length >= 1
37-
: libraries.find(library => (name === library.npmPkg ? library : undefined)),
38-
])
39-
)
30+
Object.fromEntries(packageNames.map(name => [name, handleCheck(libraries, name, check)]))
4031
);
4132
}
33+
34+
function handleCheck(libraries: LibraryType[], name: string, check?: string) {
35+
switch (check) {
36+
case 'version':
37+
const lib = libraries.find(library => (name === library.npmPkg ? library : undefined));
38+
return lib ? (lib.npm?.latestRelease ?? 'unknown') : undefined;
39+
case 'true':
40+
return libraries.some(library => name === library.npmPkg);
41+
default:
42+
return libraries.find(library => (name === library.npmPkg ? library : undefined));
43+
}
44+
}

scenes/PackageOverviewScene.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,12 @@ export default function PackageOverviewScene({
167167
</ul>
168168
</CollapsibleSection>
169169
)}
170-
<DependenciesSection title="Dependencies" data={dependencies} checkExistence />
170+
<DependenciesSection
171+
title="Dependencies"
172+
data={dependencies}
173+
checkExistence
174+
checkVersion
175+
/>
171176
<DependenciesSection
172177
title="Peer dependencies"
173178
data={mergePeerDependenciesData(registryData)}

0 commit comments

Comments
 (0)