-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(typescript): pre-load definitely typed pkg #639
Changes from all commits
3902b98
145b4d6
7b1e883
1c15265
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import * as npm from '../npm'; | ||
import { fileExistsInUnpkg } from '../unpkg'; | ||
|
||
import * as api from './index'; | ||
|
||
jest.mock('../npm'); | ||
jest.mock('../unpkg'); | ||
|
||
describe('loadTypesIndex()', () => { | ||
it('should download and cache all @types', async () => { | ||
expect(api.typesCache).not.toHaveProperty('algoliasearch'); | ||
expect(api.isDefinitelyTyped({ name: 'algoliasearch' })).toBe(undefined); | ||
|
||
await api.loadTypesIndex(); | ||
expect(api.typesCache).toHaveProperty('algoliasearch'); | ||
expect(api.typesCache).not.toHaveProperty('algoliasearch/lite'); | ||
|
||
expect(api.typesCache.algoliasearch).toBe('algoliasearch'); | ||
expect(api.typesCache['algoliasearch/lite']).toBe(undefined); | ||
expect(api.typesCache.doesnotexist).toBe(undefined); | ||
|
||
expect(api.isDefinitelyTyped({ name: 'algoliasearch' })).toBe( | ||
'algoliasearch' | ||
); | ||
}); | ||
}); | ||
|
||
describe('getTypeScriptSupport()', () => { | ||
it('If types are already calculated - return early', async () => { | ||
const typesSupport = await api.getTypeScriptSupport({ | ||
name: 'Has Types', | ||
types: { ts: 'included' }, | ||
version: '1.0', | ||
}); | ||
|
||
expect(typesSupport).toEqual({ types: { ts: 'included' } }); | ||
}); | ||
|
||
it('Handles not having any possible TS types', async () => { | ||
const typesSupport = await api.getTypeScriptSupport({ | ||
name: 'my-lib', | ||
types: { ts: false }, | ||
version: '1.0', | ||
}); | ||
expect(typesSupport).toEqual({ types: { ts: false } }); | ||
}); | ||
|
||
describe('Definitely Typed', () => { | ||
it('Checks for @types/[name]', async () => { | ||
const atTypesSupport = await api.getTypeScriptSupport({ | ||
name: 'lodash.valuesin', | ||
types: { ts: false }, | ||
version: '1.0', | ||
}); | ||
expect(atTypesSupport).toEqual({ | ||
types: { | ||
ts: 'definitely-typed', | ||
definitelyTyped: '@types/lodash.valuesin', | ||
}, | ||
}); | ||
}); | ||
|
||
it('Checks for @types/[scope__name]', async () => { | ||
const atTypesSupport = await api.getTypeScriptSupport({ | ||
name: '@mapbox/geojson-area', | ||
types: { ts: false }, | ||
version: '1.0', | ||
}); | ||
expect(atTypesSupport).toEqual({ | ||
types: { | ||
ts: 'definitely-typed', | ||
definitelyTyped: '@types/mapbox__geojson-area', | ||
}, | ||
}); | ||
|
||
const atTypesSupport2 = await api.getTypeScriptSupport({ | ||
name: '@reach/router', | ||
types: { ts: false }, | ||
version: '1.0', | ||
}); | ||
expect(atTypesSupport2).toEqual({ | ||
types: { | ||
ts: 'definitely-typed', | ||
definitelyTyped: '@types/reach__router', | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('unpkg', () => { | ||
it('Checks for a d.ts resolved version of main', async () => { | ||
// @ts-expect-error | ||
npm.validatePackageExists.mockResolvedValue(false); | ||
// @ts-expect-error | ||
fileExistsInUnpkg.mockResolvedValue(true); | ||
|
||
const typesSupport = await api.getTypeScriptSupport({ | ||
name: 'my-lib', | ||
types: { ts: { possible: true, dtsMain: 'main.d.ts' } }, | ||
version: '1.0.0', | ||
}); | ||
expect(typesSupport).toEqual({ types: { ts: 'included' } }); | ||
}); | ||
}); | ||
|
||
// TO DO : reup this | ||
// adescribe('FilesList', () => { | ||
// ait('should match a correct filesList', async () => { | ||
// const atTypesSupport = await api.getTypeScriptSupport( | ||
// { | ||
// name: 'doesnotexist', | ||
// types: { ts: false }, | ||
// version: '1.0', | ||
|
||
// }, | ||
// [{ name: 'index.js' }, { name: 'index.d.ts' }] | ||
// ); | ||
// expect(atTypesSupport).toEqual({ | ||
// types: { | ||
// _where: 'filesList', | ||
// ts: 'included', | ||
// }, | ||
// }); | ||
// }); | ||
|
||
// ait('should not match an incorrect filesList', async () => { | ||
// const atTypesSupport = await api.getTypeScriptSupport( | ||
// { | ||
// name: 'doesnotexist', | ||
// types: { ts: false }, | ||
// version: '1.0', | ||
|
||
// }, | ||
// [{ name: 'index.js' }, { name: 'index.ts' }, { name: 'index.md' }] | ||
// ); | ||
// expect(atTypesSupport).toEqual({ | ||
// types: { | ||
// ts: false, | ||
// }, | ||
// }); | ||
// }); | ||
// }); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import type { RawPkg } from '../@types/pkg'; | ||
import { config } from '../config'; | ||
import { fileExistsInUnpkg } from '../unpkg'; | ||
import { datadog } from '../utils/datadog'; | ||
import { log } from '../utils/log'; | ||
import { request } from '../utils/request'; | ||
|
||
interface TypeList { | ||
p: string; // url | ||
l: string; // display name | ||
t: string; // package name | ||
// don't known | ||
d: number; | ||
g: string[]; | ||
m: string[]; | ||
} | ||
|
||
export const typesCache: Record<string, string> = {}; | ||
|
||
/** | ||
* Microsoft build a index.json with all @types/* on each publication. | ||
* - https://github.com/microsoft/types-publisher/blob/master/src/create-search-index.ts. | ||
*/ | ||
export async function loadTypesIndex(): Promise<void> { | ||
const start = Date.now(); | ||
const { body } = await request<TypeList[]>(config.typescriptTypesIndex, { | ||
decompress: true, | ||
responseType: 'json', | ||
}); | ||
|
||
log.info(`📦 Typescript preload, found ${body.length} @types`); | ||
|
||
// m = modules associated | ||
// t = @types/<name> | ||
body.forEach((type) => { | ||
typesCache[unmangle(type.t)] = type.t; | ||
}); | ||
|
||
datadog.timing('typescript.loadTypesIndex', Date.now() - start); | ||
} | ||
|
||
export function isDefinitelyTyped({ name }): string | undefined { | ||
return typesCache[unmangle(name)]; | ||
} | ||
|
||
export function unmangle(name: string): string { | ||
// https://github.com/algolia/npm-search/pull/407/files#r316562095 | ||
return name.replace('__', '/').replace('@', ''); | ||
} | ||
|
||
/** | ||
* Basically either | ||
* - { types: { ts: false }} for no existing TypeScript support | ||
* - { types: { ts: "@types/module" }} - for definitely typed support | ||
* - { types: { ts: "included" }} - for types shipped with the module. | ||
*/ | ||
export async function getTypeScriptSupport( | ||
pkg: Pick<RawPkg, 'name' | 'types' | 'version'> | ||
): Promise<Pick<RawPkg, 'types'>> { | ||
// Already calculated in `formatPkg` | ||
if (pkg.types.ts === 'included') { | ||
return { types: pkg.types }; | ||
} | ||
|
||
// The 2nd most likely is definitely typed | ||
const defTyped = isDefinitelyTyped({ name: pkg.name }); | ||
if (defTyped) { | ||
return { | ||
types: { | ||
ts: 'definitely-typed', | ||
definitelyTyped: `@types/${defTyped}`, | ||
}, | ||
}; | ||
} | ||
|
||
if (pkg.types.ts === false) { | ||
return { types: { ts: false } }; | ||
} | ||
|
||
// Do we have a main .d.ts file? | ||
// TO DO: replace this with a list of files check | ||
if (pkg.types.ts !== 'definitely-typed' && pkg.types.ts.possible === true) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how can ts be definitely-typed here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can not but Typescript is messing with me, I prefer to have an useless if than adding |
||
const resolved = await fileExistsInUnpkg( | ||
pkg.name, | ||
pkg.version, | ||
pkg.types.ts.dtsMain | ||
); | ||
if (resolved) { | ||
return { types: { ts: 'included' } }; | ||
} | ||
} | ||
|
||
return { types: { ts: false } }; | ||
} | ||
|
||
/** | ||
* Check if packages have Typescript definitions. | ||
*/ | ||
export async function getTSSupport( | ||
pkgs: Array<Pick<RawPkg, 'name' | 'types' | 'version'>> | ||
): Promise<Array<Pick<RawPkg, 'types'>>> { | ||
const start = Date.now(); | ||
|
||
const all = await Promise.all(pkgs.map(getTypeScriptSupport)); | ||
|
||
datadog.timing('getTSSupport', Date.now() - start); | ||
return all; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's missing to add these tests back?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the aforementioned PR I was prefetching the list of files beforehand but it requires more work and it's not the slowest part of the code