npm.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { IJupyterLabPackageData } from './companions';
  4. /**
  5. * Information about a person in search results.
  6. */
  7. export interface IPerson {
  8. /**
  9. * The username of the person.
  10. */
  11. username: string;
  12. /**
  13. * The email of the person.
  14. */
  15. email: string;
  16. }
  17. /**
  18. * NPM registry search result structure (subset).
  19. *
  20. * See https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
  21. * for full specification.
  22. */
  23. export interface ISearchResult {
  24. /**
  25. * A collection of search results.
  26. */
  27. objects: {
  28. /**
  29. * Metadata about the found package.
  30. */
  31. package: {
  32. /**
  33. * The package name.
  34. */
  35. name: string;
  36. /**
  37. * The scope of the package (e.g. jupyterlab for @jupyterlab/services).
  38. */
  39. scope: string;
  40. /**
  41. * Version number.
  42. */
  43. version: string;
  44. /**
  45. * Description as listed in package.json.
  46. */
  47. description: string;
  48. /**
  49. * Package keywords.
  50. */
  51. keywords: string[];
  52. /**
  53. * Timestamp of release(?).
  54. */
  55. date: string;
  56. /**
  57. * Various metadata links for the package.
  58. */
  59. links: { [key: string]: string };
  60. /**
  61. * Metadata about user who published the release.
  62. */
  63. publisher: IPerson;
  64. /**
  65. * Maintainer list per package.json.
  66. */
  67. maintainers: IPerson[];
  68. };
  69. /**
  70. * Flags about the package.
  71. */
  72. flags: {
  73. /**
  74. * Package is insecure or have vulnerable dependencies (based on the nsp registry).
  75. */
  76. insecure: number;
  77. /**
  78. * Package has a version < 1.0.0.
  79. */
  80. unstable: boolean;
  81. };
  82. /**
  83. * Object detailing the normalized search score.
  84. */
  85. score: {
  86. /**
  87. * The final normalized search score.
  88. */
  89. final: number;
  90. /**
  91. * Break down of the search score.
  92. */
  93. detail: {
  94. /**
  95. * The normalized quality score.
  96. */
  97. quality: number;
  98. /**
  99. * The normalized popularity score.
  100. */
  101. popularity: number;
  102. /**
  103. * The normalized maintenance score.
  104. */
  105. maintenance: number;
  106. };
  107. };
  108. /**
  109. * The search score.
  110. */
  111. searchScore: number;
  112. }[];
  113. /**
  114. * The total number of objects found by the search.
  115. *
  116. * This can be greater than the number of objects due
  117. * to pagination of the search results.
  118. */
  119. total: number;
  120. /**
  121. * Timestamp of the search result creation.
  122. */
  123. time: string;
  124. }
  125. /**
  126. * An interface for a subset of the keys known to be included for package metadata.
  127. *
  128. * See https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
  129. * for full specification.
  130. */
  131. export interface IPackageMetadata {
  132. /**
  133. * The package name.
  134. */
  135. name: string;
  136. /**
  137. * ISO string of the last time this package was modified.
  138. */
  139. modified: string;
  140. /**
  141. * A mapping of dist tags to the versions they point to.
  142. */
  143. 'dist-tags': {
  144. /**
  145. * The version tagged as 'latest'.
  146. */
  147. latest: string;
  148. [key: string]: string;
  149. };
  150. /**
  151. * A short description of the package.
  152. */
  153. description: string;
  154. /**
  155. * A mapping of semver-compliant version numbers to version data.
  156. */
  157. versions: {
  158. [key: string]: {
  159. /**
  160. * The package name.
  161. */
  162. name: string;
  163. /**
  164. * The version string for this version.
  165. */
  166. version: string;
  167. /**
  168. * The deprecation warnings message of this version.
  169. */
  170. deprecated?: string;
  171. /**
  172. * A short description of the package.
  173. */
  174. description: string;
  175. };
  176. };
  177. }
  178. /**
  179. * An object for searching an NPM registry.
  180. *
  181. * Searches the NPM registry via web API:
  182. * https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
  183. */
  184. export class Searcher {
  185. /**
  186. * Create a Searcher object.
  187. *
  188. * @param repoUri The URI of the NPM registry to use.
  189. * @param cdnUri The URI of the CDN to use for fetching full package data.
  190. */
  191. constructor(
  192. repoUri = 'https://registry.npmjs.org/-/v1/',
  193. cdnUri = 'https://unpkg.com',
  194. enableCdn = true
  195. ) {
  196. this.repoUri = repoUri;
  197. this.cdnUri = cdnUri;
  198. this.enableCdn = enableCdn;
  199. }
  200. /**
  201. * Search for a jupyterlab extension.
  202. *
  203. * @param query The query to send. `keywords:"jupyterlab-extension"` will be appended to the query.
  204. * @param page The page of results to fetch.
  205. * @param pageination The pagination size to use. See registry API documentation for acceptable values.
  206. */
  207. searchExtensions(
  208. query: string,
  209. page = 0,
  210. pageination = 250
  211. ): Promise<ISearchResult> {
  212. const uri = new URL('search', this.repoUri);
  213. // Note: Spaces are encoded to '+' signs!
  214. const text = `${query} keywords:"jupyterlab-extension"`;
  215. uri.searchParams.append('text', text);
  216. uri.searchParams.append('size', pageination.toString());
  217. uri.searchParams.append('from', (pageination * page).toString());
  218. return fetch(uri.toString()).then((response: Response) => {
  219. if (response.ok) {
  220. return response.json();
  221. }
  222. return [];
  223. });
  224. }
  225. /**
  226. * Fetch package.json of a package
  227. *
  228. * @param name The package name.
  229. * @param version The version of the package to fetch.
  230. */
  231. fetchPackageData(
  232. name: string,
  233. version: string
  234. ): Promise<IJupyterLabPackageData | null> {
  235. const uri = this.enableCdn
  236. ? new URL(`/${name}@${version}/package.json`, this.cdnUri)
  237. : new URL(`/${name}/${version}`, this.repoUri);
  238. return fetch(uri.toString()).then((response: Response) => {
  239. if (response.ok) {
  240. return response.json();
  241. }
  242. return null;
  243. });
  244. }
  245. /**
  246. * The URI of the NPM registry to use.
  247. */
  248. repoUri: string;
  249. /**
  250. * The URI of the CDN to use for fetching full package data.
  251. */
  252. cdnUri: string;
  253. /**
  254. * Whether to use the CDN for fetching full package data. Otherwise, the NPM registry is used.
  255. */
  256. enableCdn: boolean;
  257. }
  258. /**
  259. * Check whether the NPM org is a Jupyter one.
  260. */
  261. export function isJupyterOrg(name: string): boolean {
  262. /**
  263. * A list of jupyterlab NPM orgs.
  264. */
  265. const jupyterOrg = ['jupyterlab', 'jupyter-widgets'];
  266. const parts = name.split('/');
  267. const first = parts[0];
  268. return (
  269. parts.length > 1 && // Has a first part
  270. !!first && // with a finite length
  271. first[0] === '@' && // corresponding to an org name
  272. jupyterOrg.indexOf(first.slice(1)) !== -1 // in the org allowedExtensions.
  273. );
  274. }