Skip to content

Commit dd386d2

Browse files
Samuel BodinHaroenv
Samuel Bodin
andauthored
feat(changelog): get from jsDelivr filelist if possible (#640)
Co-authored-by: Haroen Viaene <[email protected]>
1 parent dba5d2a commit dd386d2

File tree

3 files changed

+145
-27
lines changed

3 files changed

+145
-27
lines changed

src/__tests__/changelog.test.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { getChangelogs, baseUrlMap } from '../changelog';
1+
import { getChangelogs, baseUrlMap, getChangelog } from '../changelog';
2+
import * as jsDelivr from '../jsDelivr';
23

34
jest.mock('got', () => {
45
const gotSnapshotUrls = new Set([
@@ -16,6 +17,12 @@ jest.mock('got', () => {
1617
};
1718
});
1819

20+
const spy = jest
21+
.spyOn(jsDelivr, 'getFilesList')
22+
.mockImplementation((): Promise<any[]> => {
23+
return Promise.resolve([]);
24+
});
25+
1926
describe('should test baseUrlMap', () => {
2027
it('should work with paths', () => {
2128
const bitbucketRepo = {
@@ -210,3 +217,76 @@ it('should work with HISTORY.md', async () => {
210217
'https://raw.githubusercontent.com/expressjs/body-parser/master/HISTORY.md'
211218
);
212219
});
220+
221+
describe('jsDelivr', () => {
222+
it('should early return when finding changelog', async () => {
223+
spy.mockResolvedValue([
224+
{ name: '/package.json', hash: '', time: '1', size: 1 },
225+
{ name: '/CHANGELOG.md', hash: '', time: '1', size: 1 },
226+
]);
227+
228+
const { changelogFilename } = await getChangelog({
229+
name: 'foo',
230+
version: '1.0.0',
231+
repository: {
232+
url: '',
233+
host: 'github.com',
234+
user: 'expressjs',
235+
project: 'body-parser',
236+
path: '',
237+
head: 'master',
238+
branch: 'master',
239+
},
240+
});
241+
expect(jsDelivr.getFilesList).toHaveBeenCalled();
242+
expect(changelogFilename).toEqual(
243+
'https://cdn.jsdelivr.net/npm/[email protected]/CHANGELOG.md'
244+
);
245+
});
246+
247+
it('should early return when finding changelog in nested file', async () => {
248+
spy.mockResolvedValue([
249+
{ name: '/pkg/CHANGELOG.md', hash: '', time: '1', size: 1 },
250+
]);
251+
252+
const { changelogFilename } = await getChangelog({
253+
name: 'foo',
254+
version: '1.0.0',
255+
repository: {
256+
url: '',
257+
host: 'github.com',
258+
user: 'expressjs',
259+
project: 'body-parser',
260+
path: '',
261+
head: 'master',
262+
branch: 'master',
263+
},
264+
});
265+
expect(jsDelivr.getFilesList).toHaveBeenCalled();
266+
expect(changelogFilename).toEqual(
267+
'https://cdn.jsdelivr.net/npm/[email protected]/pkg/CHANGELOG.md'
268+
);
269+
});
270+
271+
it('should not register a file looking like a changelog', async () => {
272+
spy.mockResolvedValue([
273+
{ name: '/dist/changelog.js', hash: '', time: '1', size: 1 },
274+
]);
275+
276+
const { changelogFilename } = await getChangelog({
277+
name: 'foo',
278+
version: '1.0.0',
279+
repository: {
280+
url: '',
281+
host: 'github.com',
282+
user: 'hello',
283+
project: 'foo',
284+
path: '',
285+
head: 'master',
286+
branch: 'master',
287+
},
288+
});
289+
expect(jsDelivr.getFilesList).toHaveBeenCalled();
290+
expect(changelogFilename).toEqual(null);
291+
});
292+
});

src/changelog.ts

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
1+
import path from 'path';
2+
13
import race from 'promise-rat-race';
24

35
import type { RawPkg, Repo } from './@types/pkg';
46
import { config } from './config';
7+
import * as jsDelivr from './jsDelivr/index';
58
import { datadog } from './utils/datadog';
69
import { request } from './utils/request';
710

811
export const baseUrlMap = new Map<
912
string,
1013
(opts: Pick<Repo, 'user' | 'project' | 'path' | 'branch'>) => string
1114
>();
12-
baseUrlMap.set('github.com', ({ user, project, path, branch }): string => {
13-
return `https://raw.githubusercontent.com/${user}/${project}/${
14-
path ? '' : branch
15-
}${`${path.replace('/tree/', '')}`}`;
16-
});
17-
baseUrlMap.set('gitlab.com', ({ user, project, path, branch }): string => {
18-
return `https://gitlab.com/${user}/${project}${
19-
path ? path.replace('tree', 'raw') : `/raw/${branch}`
20-
}`;
21-
});
22-
baseUrlMap.set('bitbucket.org', ({ user, project, path, branch }): string => {
23-
return `https://bitbucket.org/${user}/${project}${
24-
path ? path.replace('src', 'raw') : `/raw/${branch}`
25-
}`;
26-
});
15+
baseUrlMap.set(
16+
'github.com',
17+
({ user, project, path: pathName, branch }): string => {
18+
return `https://raw.githubusercontent.com/${user}/${project}/${
19+
pathName ? '' : branch
20+
}${`${pathName.replace('/tree/', '')}`}`;
21+
}
22+
);
23+
baseUrlMap.set(
24+
'gitlab.com',
25+
({ user, project, path: pathName, branch }): string => {
26+
return `https://gitlab.com/${user}/${project}${
27+
pathName ? pathName.replace('tree', 'raw') : `/raw/${branch}`
28+
}`;
29+
}
30+
);
31+
baseUrlMap.set(
32+
'bitbucket.org',
33+
({ user, project, path: pathName, branch }): string => {
34+
return `https://bitbucket.org/${user}/${project}${
35+
pathName ? pathName.replace('src', 'raw') : `/raw/${branch}`
36+
}`;
37+
}
38+
);
2739

2840
const fileOptions = [
2941
'CHANGELOG.md',
@@ -43,8 +55,14 @@ const fileOptions = [
4355
'history.md',
4456
'HISTORY',
4557
'history',
58+
'RELEASES.md',
59+
'RELEASES',
4660
];
4761

62+
// https://regex101.com/r/zU2gjr/1
63+
const fileRegex =
64+
/^(((changelogs?)|changes|history|(releases?)))((.(md|markdown))?$)/i;
65+
4866
async function handledGot(file: string): Promise<string> {
4967
const result = await request(file, { method: 'HEAD' });
5068

@@ -76,13 +94,27 @@ async function raceFromPaths(files: string[]): Promise<{
7694
}
7795
}
7896

79-
function getChangelog({
80-
repository,
81-
name,
82-
version,
83-
}: Pick<RawPkg, 'repository' | 'name' | 'version'>): Promise<{
97+
export async function getChangelog(
98+
pkg: Pick<RawPkg, 'repository' | 'name' | 'version'>
99+
): Promise<{
84100
changelogFilename: string | null;
85101
}> {
102+
// Do a quick call to jsDelivr
103+
// Only work if the package has published their changelog along with the code
104+
const filesList = await jsDelivr.getFilesList(pkg);
105+
for (const file of filesList) {
106+
const name = path.basename(file.name);
107+
if (!fileRegex.test(name)) {
108+
// eslint-disable-next-line no-continue
109+
continue;
110+
}
111+
112+
return { changelogFilename: jsDelivr.getFullURL(pkg, file) };
113+
}
114+
115+
const { repository, name, version } = pkg;
116+
117+
// Rollback to brute-force the source code
86118
const unpkgFiles = fileOptions.map(
87119
(file) => `${config.unpkgRoot}/${name}@${version}/${file}`
88120
);

src/jsDelivr/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { request } from '../utils/request';
66

77
type Hit = { type: 'npm'; name: string; hits: number };
88
type File = { name: string; hash: string; time: string; size: number };
9-
const hits = new Map<string, number>();
9+
10+
export const hits = new Map<string, number>();
1011

1112
/**
1213
* Load downloads hits.
1314
*/
14-
async function loadHits(): Promise<void> {
15+
export async function loadHits(): Promise<void> {
1516
const start = Date.now();
1617
log.info('📦 Loading hits from jsDelivr');
1718

@@ -34,7 +35,7 @@ async function loadHits(): Promise<void> {
3435
/**
3536
* Get download hits.
3637
*/
37-
function getHits(pkgs: Array<Pick<RawPkg, 'name'>>): Array<{
38+
export function getHits(pkgs: Array<Pick<RawPkg, 'name'>>): Array<{
3839
jsDelivrHits: number;
3940
_searchInternal: { jsDelivrPopularity: number };
4041
}> {
@@ -59,7 +60,7 @@ function getHits(pkgs: Array<Pick<RawPkg, 'name'>>): Array<{
5960
/**
6061
* Get packages files list.
6162
*/
62-
async function getAllFilesList(
63+
export async function getAllFilesList(
6364
pkgs: Array<Pick<RawPkg, 'name' | 'version'>>
6465
): Promise<File[][]> {
6566
const start = Date.now();
@@ -73,7 +74,7 @@ async function getAllFilesList(
7374
/**
7475
* Get one package files list.
7576
*/
76-
async function getFilesList(
77+
export async function getFilesList(
7778
pkg: Pick<RawPkg, 'name' | 'version'>
7879
): Promise<File[]> {
7980
const start = Date.now();
@@ -100,4 +101,9 @@ async function getFilesList(
100101
return files;
101102
}
102103

103-
export { hits, loadHits, getHits, getAllFilesList, getFilesList };
104+
export function getFullURL(
105+
pkg: Pick<RawPkg, 'name' | 'version'>,
106+
file: File
107+
): string {
108+
return `https://cdn.jsdelivr.net/npm/${pkg.name}@${pkg.version}${file.name}`;
109+
}

0 commit comments

Comments
 (0)