make-fetch-happen.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import path from 'node:path';
  2. import fs from 'node:fs';
  3. import makeFetchHappen from 'make-fetch-happen';
  4. import type { FetchOptions } from 'make-fetch-happen';
  5. import cacache from 'cacache';
  6. import picocolors from 'picocolors';
  7. // eslint-disable-next-line @typescript-eslint/no-restricted-imports -- type only
  8. import type { Response as NodeFetchResponse } from 'node-fetch';
  9. import { task } from '../trace';
  10. import { bytes } from 'xbits';
  11. export type { NodeFetchResponse };
  12. const cachePath = path.resolve(__dirname, '../../.cache/__make_fetch_happen__');
  13. fs.mkdirSync(cachePath, { recursive: true });
  14. interface CacacheVerifyStats {
  15. startTime: Date,
  16. endTime: Date,
  17. runTime: {
  18. markStartTime: 0,
  19. fixPerms: number,
  20. garbageCollect: number,
  21. rebuildIndex: number,
  22. cleanTmp: number,
  23. writeVerifile: number,
  24. markEndTime: number,
  25. total: number
  26. },
  27. verifiedContent: number,
  28. reclaimedCount: number,
  29. reclaimedSize: number,
  30. badContentCount: number,
  31. keptSize: number,
  32. missingContent: number,
  33. rejectedEntries: number,
  34. totalEntries: number
  35. }
  36. export const cacheGc = task(require.main === module, __filename)(
  37. (span) => span
  38. .traceChildAsync('cacache gc', () => cacache.verify(cachePath, { concurrency: 64 }))
  39. .then((stats: CacacheVerifyStats) => {
  40. // console.log({ stats });
  41. console.log(picocolors.green('[cacheGc] running gc on cache:'), cachePath);
  42. console.log(picocolors.green('[cacheGc] content verified:'), stats.verifiedContent, '(' + bytes(stats.keptSize) + ')');
  43. console.log(picocolors.green('[cacheGc] reclaimed:'), stats.reclaimedCount, '(' + bytes(stats.reclaimedSize) + ')');
  44. })
  45. );
  46. const _fetch = makeFetchHappen.defaults({
  47. cachePath,
  48. maxSockets: 32, /**
  49. * They said 15 is a good default that prevents knocking out others' routers,
  50. * I disagree. 32 is a good number.
  51. */
  52. headers: {
  53. 'User-Agent': 'curl/8.9.1 (https://github.com/SukkaW/Surge)'
  54. },
  55. retry: {
  56. retries: 5,
  57. randomize: true
  58. }
  59. });
  60. export function $fetch(uriOrRequest: string | Request, opts?: FetchOptions) {
  61. return _fetch(uriOrRequest, opts).then((resp) => {
  62. printResponseStatus(resp);
  63. return resp;
  64. });
  65. }
  66. export async function $delete(resp: NodeFetchResponse) {
  67. const cacheKey = resp.headers.get('X-Local-Cache-Key');
  68. if (cacheKey) {
  69. await cacache.rm.entry(cachePath, cacheKey);
  70. await cacache.verify(cachePath, { concurrency: 64 });
  71. }
  72. }
  73. export function printResponseStatus(resp: NodeFetchResponse) {
  74. const status = resp.headers.get('X-Local-Cache-Status');
  75. if (status) {
  76. console.log('[$fetch cache]', { status }, picocolors.gray(resp.url));
  77. }
  78. }