@rushstack/rush-http-build-cache-plugin 5.97.1-pr3481.18 → 5.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.heft/build-cache/jest-cache/haste-map-bae913f9b9aa720eb4deeae0a60a4b27-22ae7f4ce9de4306889d8c05e5cc39b9-f6b1af01a3130057bdfe3d86807211f9 +0 -0
  2. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{42/Import_42731f0220476b7d949a2d8f3a6f3333 → 12/Import_12af6d2ca109ffba883fc5079062e0e8} +157 -14
  3. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{c7/commons_c77bee95093c1098b4a591f2dadf398d → 2e/commons_2e52897dfe4222d991674613f5b35882} +1007 -2581
  4. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{26/package_263c59db6933ba9581588638fb19da5c → 2f/package_2f923ac6ad9bbd4966f8472750ef7770} +2 -2
  5. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/34/PrefixProxyTerminalProvider_34b4603b9c4455de1c3cc05820149fdd +65 -0
  6. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{58/index_58c57a8f2a96bf6fdf5db35a2e44baa7 → 46/index_46761981528d09d1a0e438f06cd2c548} +7 -2
  7. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{06/ConfigurationFile_06d7f35feb0bd944d9b9a2a4d9e112c8 → 57/ConfigurationFile_57c950e8ef4673e11bff94304a50823c} +10 -4
  8. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{fa/rushlib_fa830331d24a41292b5cb91040384188 → 69/rushlib_695f6e29167e91482e2906a8d09b3a5a} +17 -28
  9. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/8a/HttpBuildCacheProvidertest_8a1598e1f1b42d6933964acfb7612893 +116 -0
  10. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/8a/HttpBuildCacheProvidertest_8a1598e1f1b42d6933964acfb7612893.map +1 -0
  11. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/a3/RushHttpBuildCachePlugin_a3e8a9bb172ff361907e770dcf3268be +1551 -0
  12. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/a3/RushHttpBuildCachePlugin_a3e8a9bb172ff361907e770dcf3268be.map +1 -0
  13. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{c2/Async_c2ea5cc2edc0a460dcf394ef8277e88e → b4/Async_b43c9d64ab6a035dbce65b9f1a28e166} +78 -2
  14. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/ba/TerminalWritable_ba71b4b7bfe6a26c85f4fd0245057a0a +54 -0
  15. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/ce/index_ce3464019fb882539ee9a6f3e36e615a +2 -2
  16. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/ce/index_ce3464019fb882539ee9a6f3e36e615a.map +1 -1
  17. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{5d/Text_5da85b5db9a2f06bbe29cdcd91e6fbca → df/Text_df57ddd200e4237e617fc183dcb5fe67} +7 -1
  18. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/{e5/HttpBuildCacheProvider_e53bd002cf44ae62693f20b8c4682941 → ec/HttpBuildCacheProvider_ecceab34e8340fa24dde11b618ffa8a4} +1210 -909
  19. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/ec/HttpBuildCacheProvider_ecceab34e8340fa24dde11b618ffa8a4.map +1 -0
  20. package/.heft/build-cache/jest-cache/perf-cache-bae913f9b9aa720eb4deeae0a60a4b27-da39a3ee5e6b4b0d3255bfef95601890 +1 -1
  21. package/.rush/temp/operation/_phase_build/state.json +1 -1
  22. package/.rush/temp/operation/_phase_test/all.log +29 -0
  23. package/.rush/temp/operation/_phase_test/state.json +1 -1
  24. package/.rush/temp/package-deps__phase_build.json +5 -5
  25. package/.rush/temp/package-deps__phase_test.json +5 -5
  26. package/.rush/temp/rushstack+rush-http-build-cache-plugin-_phase_build-08118e08afc6d454e5623f20498fd64b5e3e9051.log +10 -0
  27. package/.rush/temp/shrinkwrap-deps.json +10 -10
  28. package/lib/HttpBuildCacheProvider.d.ts +2 -0
  29. package/lib/HttpBuildCacheProvider.d.ts.map +1 -1
  30. package/lib/HttpBuildCacheProvider.js +26 -7
  31. package/lib/HttpBuildCacheProvider.js.map +1 -1
  32. package/lib/RushHttpBuildCachePlugin.d.ts.map +1 -1
  33. package/lib/RushHttpBuildCachePlugin.js +28 -4
  34. package/lib/RushHttpBuildCachePlugin.js.map +1 -1
  35. package/lib/test/HttpBuildCacheProvider.test.d.ts.map +1 -0
  36. package/lib/{HttpBuildCacheProvider.test.js → test/HttpBuildCacheProvider.test.js} +46 -3
  37. package/lib/test/HttpBuildCacheProvider.test.js.map +1 -0
  38. package/package.json +6 -6
  39. package/rush-logs/rush-http-build-cache-plugin._phase_build.log +4 -23
  40. package/rush-logs/rush-http-build-cache-plugin._phase_test.log +16 -16
  41. package/src/HttpBuildCacheProvider.ts +36 -7
  42. package/src/RushHttpBuildCachePlugin.ts +23 -28
  43. package/src/test/HttpBuildCacheProvider.test.ts +115 -0
  44. package/temp/coverage/clover.xml +117 -109
  45. package/temp/coverage/coverage-final.json +2 -2
  46. package/temp/coverage/lcov-report/HttpBuildCacheProvider.ts.html +166 -79
  47. package/temp/coverage/lcov-report/RushHttpBuildCachePlugin.ts.html +29 -44
  48. package/temp/coverage/lcov-report/index.html +24 -24
  49. package/temp/coverage/lcov-report/index.ts.html +1 -1
  50. package/temp/coverage/lcov.info +236 -218
  51. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/e5/HttpBuildCacheProvider_e53bd002cf44ae62693f20b8c4682941.map +0 -1
  52. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/e6/RushHttpBuildCachePlugin_e69b4b7cd4f95aa49455690c88f00247 +0 -362
  53. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/e6/RushHttpBuildCachePlugin_e69b4b7cd4f95aa49455690c88f00247.map +0 -1
  54. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/f7/HttpBuildCacheProvidertest_f710af633835df558cad54cb165c14fa +0 -73
  55. package/.heft/build-cache/jest-cache/jest-transform-cache-bae913f9b9aa720eb4deeae0a60a4b27-474488b31a4a940a3990e9eaf06f1647/f7/HttpBuildCacheProvidertest_f710af633835df558cad54cb165c14fa.map +0 -1
  56. package/.rush/temp/rushstack+rush-http-build-cache-plugin-_phase_build-5e3cfa01506c2ac77af487ee9732b935a5bbe122.log +0 -27
  57. package/lib/HttpBuildCacheProvider.test.d.ts.map +0 -1
  58. package/lib/HttpBuildCacheProvider.test.js.map +0 -1
  59. package/src/HttpBuildCacheProvider.test.ts +0 -58
  60. /package/lib/{HttpBuildCacheProvider.test.d.ts → test/HttpBuildCacheProvider.test.d.ts} +0 -0
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3
+ // See LICENSE in the project root for license information.
2
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
5
  if (k2 === undefined) k2 = k;
4
6
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -27,9 +29,9 @@ jest.mock('node-fetch', function () {
27
29
  return Object.assign(jest.fn(), jest.requireActual('node-fetch'));
28
30
  });
29
31
  const node_fetch_1 = __importStar(require("node-fetch"));
30
- const HttpBuildCacheProvider_1 = require("./HttpBuildCacheProvider");
31
32
  const rush_sdk_1 = require("@rushstack/rush-sdk");
32
33
  const node_core_library_1 = require("@rushstack/node-core-library");
34
+ const HttpBuildCacheProvider_1 = require("../HttpBuildCacheProvider");
33
35
  const EXAMPLE_OPTIONS = {
34
36
  url: 'https://buildcache.example.acme.com',
35
37
  tokenHandler: {
@@ -39,7 +41,8 @@ const EXAMPLE_OPTIONS = {
39
41
  uploadMethod: 'POST',
40
42
  isCacheWriteAllowed: false,
41
43
  pluginName: 'example-plugin',
42
- rushProjectRoot: '/repo'
44
+ rushProjectRoot: '/repo',
45
+ minHttpRetryDelayMs: 1
43
46
  };
44
47
  describe('HttpBuildCacheProvider', () => {
45
48
  let terminalBuffer;
@@ -59,7 +62,8 @@ describe('HttpBuildCacheProvider', () => {
59
62
  }));
60
63
  const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
61
64
  expect(result).toBe(undefined);
62
- expect(node_fetch_1.default).toHaveBeenCalledWith('https://buildcache.example.acme.com/some-key', {
65
+ expect(node_fetch_1.default).toHaveBeenCalledTimes(1);
66
+ expect(node_fetch_1.default).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {
63
67
  body: undefined,
64
68
  headers: {},
65
69
  method: 'GET',
@@ -67,6 +71,45 @@ describe('HttpBuildCacheProvider', () => {
67
71
  });
68
72
  expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(`"Error getting cache entry: Error: Credentials for https://buildcache.example.acme.com/ have not been provided.[n]In CI, verify that RUSH_BUILD_CACHE_CREDENTIAL contains a valid Authorization header value.[n][n]For local developers, run:[n][n] rush update-cloud-credentials --interactive[n][n]"`);
69
73
  });
74
+ it('attempts up to 3 times to download a cache entry', async () => {
75
+ jest.spyOn(rush_sdk_1.EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);
76
+ const session = {};
77
+ const provider = new HttpBuildCacheProvider_1.HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);
78
+ mocked(node_fetch_1.default).mockResolvedValueOnce(new node_fetch_1.Response('InternalServiceError', {
79
+ status: 500,
80
+ statusText: 'InternalServiceError'
81
+ }));
82
+ mocked(node_fetch_1.default).mockResolvedValueOnce(new node_fetch_1.Response('ServiceUnavailable', {
83
+ status: 503,
84
+ statusText: 'ServiceUnavailable'
85
+ }));
86
+ mocked(node_fetch_1.default).mockResolvedValueOnce(new node_fetch_1.Response('BadGateway', {
87
+ status: 504,
88
+ statusText: 'BadGateway'
89
+ }));
90
+ const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
91
+ expect(result).toBe(undefined);
92
+ expect(node_fetch_1.default).toHaveBeenCalledTimes(3);
93
+ expect(node_fetch_1.default).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {
94
+ body: undefined,
95
+ headers: {},
96
+ method: 'GET',
97
+ redirect: 'follow'
98
+ });
99
+ expect(node_fetch_1.default).toHaveBeenNthCalledWith(2, 'https://buildcache.example.acme.com/some-key', {
100
+ body: undefined,
101
+ headers: {},
102
+ method: 'GET',
103
+ redirect: 'follow'
104
+ });
105
+ expect(node_fetch_1.default).toHaveBeenNthCalledWith(3, 'https://buildcache.example.acme.com/some-key', {
106
+ body: undefined,
107
+ headers: {},
108
+ method: 'GET',
109
+ redirect: 'follow'
110
+ });
111
+ expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(`"Could not get cache entry: HTTP 504: BadGateway[n]"`);
112
+ });
70
113
  });
71
114
  });
72
115
  //# sourceMappingURL=HttpBuildCacheProvider.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpBuildCacheProvider.test.js","sourceRoot":"","sources":["../../src/test/HttpBuildCacheProvider.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\njest.mock('node-fetch', function () {\n return Object.assign(jest.fn(), jest.requireActual('node-fetch'));\n});\n\nimport fetch, { Response } from 'node-fetch';\nimport { RushSession, EnvironmentConfiguration } from '@rushstack/rush-sdk';\nimport { StringBufferTerminalProvider, Terminal } from '@rushstack/node-core-library';\n\nimport { HttpBuildCacheProvider } from '../HttpBuildCacheProvider';\n\nconst EXAMPLE_OPTIONS = {\n url: 'https://buildcache.example.acme.com',\n tokenHandler: {\n exec: 'node',\n args: ['tokenHandler.js']\n },\n uploadMethod: 'POST',\n isCacheWriteAllowed: false,\n pluginName: 'example-plugin',\n rushProjectRoot: '/repo',\n minHttpRetryDelayMs: 1\n};\n\ndescribe('HttpBuildCacheProvider', () => {\n let terminalBuffer: StringBufferTerminalProvider;\n let terminal!: Terminal;\n\n beforeEach(() => {\n terminalBuffer = new StringBufferTerminalProvider();\n terminal = new Terminal(terminalBuffer);\n });\n\n describe('tryGetCacheEntryBufferByIdAsync', () => {\n it('prints warning if read credentials are not available', async () => {\n jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);\n\n const session: RushSession = {} as RushSession;\n const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);\n\n mocked(fetch).mockResolvedValue(\n new Response('Unauthorized', {\n status: 401,\n statusText: 'Unauthorized'\n })\n );\n\n const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');\n expect(result).toBe(undefined);\n expect(fetch).toHaveBeenCalledTimes(1);\n expect(fetch).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {\n body: undefined,\n headers: {},\n method: 'GET',\n redirect: 'follow'\n });\n expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(\n `\"Error getting cache entry: Error: Credentials for https://buildcache.example.acme.com/ have not been provided.[n]In CI, verify that RUSH_BUILD_CACHE_CREDENTIAL contains a valid Authorization header value.[n][n]For local developers, run:[n][n] rush update-cloud-credentials --interactive[n][n]\"`\n );\n });\n\n it('attempts up to 3 times to download a cache entry', async () => {\n jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);\n\n const session: RushSession = {} as RushSession;\n const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);\n\n mocked(fetch).mockResolvedValueOnce(\n new Response('InternalServiceError', {\n status: 500,\n statusText: 'InternalServiceError'\n })\n );\n mocked(fetch).mockResolvedValueOnce(\n new Response('ServiceUnavailable', {\n status: 503,\n statusText: 'ServiceUnavailable'\n })\n );\n mocked(fetch).mockResolvedValueOnce(\n new Response('BadGateway', {\n status: 504,\n statusText: 'BadGateway'\n })\n );\n\n const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');\n expect(result).toBe(undefined);\n expect(fetch).toHaveBeenCalledTimes(3);\n expect(fetch).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {\n body: undefined,\n headers: {},\n method: 'GET',\n redirect: 'follow'\n });\n expect(fetch).toHaveBeenNthCalledWith(2, 'https://buildcache.example.acme.com/some-key', {\n body: undefined,\n headers: {},\n method: 'GET',\n redirect: 'follow'\n });\n expect(fetch).toHaveBeenNthCalledWith(3, 'https://buildcache.example.acme.com/some-key', {\n body: undefined,\n headers: {},\n method: 'GET',\n redirect: 'follow'\n });\n expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(\n `\"Could not get cache entry: HTTP 504: BadGateway[n]\"`\n );\n });\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/rush-http-build-cache-plugin",
3
- "version": "5.97.1-pr3481.18",
3
+ "version": "5.98.0",
4
4
  "description": "Rush plugin for generic HTTP cloud build cache",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,17 +14,17 @@
14
14
  "dependencies": {
15
15
  "https-proxy-agent": "~5.0.0",
16
16
  "node-fetch": "2.6.7",
17
- "@rushstack/node-core-library": "3.55.2",
18
- "@rushstack/rush-sdk": "5.97.1-pr3481.18"
17
+ "@rushstack/node-core-library": "3.59.0",
18
+ "@rushstack/rush-sdk": "5.98.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/heft-jest": "1.0.1",
22
22
  "@types/node": "14.18.36",
23
23
  "@types/node-fetch": "2.6.2",
24
- "@microsoft/rush-lib": "5.97.1-pr3481.18",
24
+ "@microsoft/rush-lib": "5.98.0",
25
25
  "@rushstack/eslint-config": "3.2.0",
26
- "@rushstack/heft": "0.50.0",
27
- "@rushstack/heft-node-rig": "1.12.6"
26
+ "@rushstack/heft": "0.50.5",
27
+ "@rushstack/heft-node-rig": "1.12.11"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "heft build --clean",
@@ -1,24 +1,5 @@
1
- Invoking: heft build --clean --production
2
- Using local Heft from /home/vsts/work/1/s/rush-plugins/rush-http-build-cache-plugin/node_modules/@rushstack/heft
1
+ Build cache hit.
2
+ Clearing cached folders: dist, lib, lib-commonjs, temp
3
+ Successfully restored output from the build cache.
3
4
 
4
- Project build folder is "/home/vsts/work/1/s/rush-plugins/rush-http-build-cache-plugin"
5
- Using rig configuration from ./node_modules/@rushstack/heft-node-rig/profiles/default
6
- Starting build
7
- ---- Clean started ----
8
- ---- Clean finished (2ms) ----
9
- ---- Pre-compile started ----
10
- ---- Pre-compile finished (0ms) ----
11
- ---- Compile started ----
12
- [copy-static-assets] Copied 1 file and linked 0 files in 12ms
13
- [typescript] Using TypeScript version 4.8.4
14
- ---- Compile finished (5243ms) ----
15
- ---- Bundle started ----
16
- ---- Bundle finished (1ms) ----
17
- ---- Post-build started ----
18
- ---- Post-build finished (2ms) ----
19
- -------------------- Finished (5.609s) --------------------
20
- Project: @rushstack/rush-http-build-cache-plugin@5.97.1-pr3481.18
21
- Heft version: 0.50.0
22
- Node version: v14.21.3
23
- Caching build output folders: lib
24
- Successfully set cache entry.
5
+ Restoring cached log file at /home/vsts/work/1/s/rush-plugins/rush-http-build-cache-plugin/.rush/temp/operation/_phase_build/all.log
@@ -1,3 +1,4 @@
1
+ This project does not define the caching behavior of the "_phase:test" command, so caching has been disabled.
1
2
  Invoking: heft test --no-build --production
2
3
  Using local Heft from /home/vsts/work/1/s/rush-plugins/rush-http-build-cache-plugin/node_modules/@rushstack/heft
3
4
 
@@ -7,23 +8,22 @@ Starting test
7
8
  [jest] Using Jest version 29.5.0
8
9
 
9
10
  Run start. 1 test suite
10
- START src/HttpBuildCacheProvider.test.ts
11
- PASS src/HttpBuildCacheProvider.test.ts (duration: 1.231s, 1 passed, 0 failed)
11
+ START src/test/HttpBuildCacheProvider.test.ts
12
+ PASS src/test/HttpBuildCacheProvider.test.ts (duration: 0.693s, 2 passed, 0 failed)
12
13
 
13
14
  Tests finished:
14
- Successes: 1
15
+ Successes: 2
15
16
  Failures: 0
16
- Total: 1
17
- -----------------------------|---------|----------|---------|---------|---------------------------------------------------
18
- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
19
- -----------------------------|---------|----------|---------|---------|---------------------------------------------------
20
- All files | 42.74 | 39.02 | 40.9 | 42.74 |
21
- HttpBuildCacheProvider.ts | 47.45 | 39.02 | 50 | 47.45 | 58,89,101-174,184,209,213-214,237-249,258,297-380
22
- RushHttpBuildCachePlugin.ts | 0 | 100 | 0 | 0 | 1-81
23
- index.ts | 0 | 100 | 100 | 0 | 1-3
24
- -----------------------------|---------|----------|---------|---------|---------------------------------------------------
25
- -------------------- Finished (2.192s) --------------------
26
- Project: @rushstack/rush-http-build-cache-plugin@5.97.1-pr3481.18
27
- Heft version: 0.50.0
17
+ Total: 2
18
+ -----------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------
19
+ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
20
+ -----------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------
21
+ All files | 53.23 | 45.55 | 47.82 | 53.23 |
22
+ HttpBuildCacheProvider.ts | 58.26 | 45.55 | 61.11 | 58.26 | 63,108-182,192,218,222-223,270-278,287,326-328,335,348-371,389-390,399-409
23
+ RushHttpBuildCachePlugin.ts | 0 | 100 | 0 | 0 | 7-77
24
+ index.ts | 0 | 100 | 100 | 0 | 1-3
25
+ -----------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------
26
+ -------------------- Finished (1.935s) --------------------
27
+ Project: @rushstack/rush-http-build-cache-plugin@5.97.1
28
+ Heft version: 0.50.5
28
29
  Node version: v14.21.3
29
- This project does not define the caching behavior of the "_phase:test" command, so caching has been disabled.
@@ -1,4 +1,4 @@
1
- import { ITerminal, Executable } from '@rushstack/node-core-library';
1
+ import { ITerminal, Executable, Async } from '@rushstack/node-core-library';
2
2
  import {
3
3
  ICloudBuildCacheProvider,
4
4
  ICredentialCacheEntry,
@@ -34,6 +34,7 @@ export interface IHttpBuildCacheProviderOptions {
34
34
  url: string;
35
35
  tokenHandler?: IHttpBuildCacheTokenHandler;
36
36
  uploadMethod?: string;
37
+ minHttpRetryDelayMs?: number;
37
38
  headers?: Record<string, string>;
38
39
  cacheKeyPrefix?: string;
39
40
  isCacheWriteAllowed: boolean;
@@ -41,6 +42,9 @@ export interface IHttpBuildCacheProviderOptions {
41
42
  rushProjectRoot: string;
42
43
  }
43
44
 
45
+ const MAX_HTTP_CACHE_ATTEMPTS: number = 3;
46
+ const DEFAULT_MIN_HTTP_RETRY_DELAY_MS = 2500;
47
+
44
48
  export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
45
49
  private readonly _pluginName: string;
46
50
  private readonly _rushSession: RushSession;
@@ -52,6 +56,7 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
52
56
  private readonly _headers: Record<string, string>;
53
57
  private readonly _cacheKeyPrefix: string;
54
58
  private readonly _tokenHandler: IHttpBuildCacheTokenHandler | undefined;
59
+ private readonly _minHttpRetryDelayMs: number;
55
60
  private __credentialCacheId: string | undefined;
56
61
 
57
62
  public get isCacheWriteAllowed(): boolean {
@@ -70,6 +75,7 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
70
75
  this._headers = options.headers ?? {};
71
76
  this._tokenHandler = options.tokenHandler;
72
77
  this._cacheKeyPrefix = options.cacheKeyPrefix ?? '';
78
+ this._minHttpRetryDelayMs = options.minHttpRetryDelayMs ?? DEFAULT_MIN_HTTP_RETRY_DELAY_MS;
73
79
  }
74
80
 
75
81
  public async tryGetCacheEntryBufferByIdAsync(
@@ -83,7 +89,8 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
83
89
  method: 'GET',
84
90
  body: undefined,
85
91
  warningText: 'Could not get cache entry',
86
- readBody: true
92
+ readBody: true,
93
+ maxAttempts: MAX_HTTP_CACHE_ATTEMPTS
87
94
  });
88
95
 
89
96
  return Buffer.isBuffer(result) ? result : undefined;
@@ -112,7 +119,8 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
112
119
  method: this._uploadMethod,
113
120
  body: objectBuffer,
114
121
  warningText: 'Could not write cache entry',
115
- readBody: false
122
+ readBody: false,
123
+ maxAttempts: MAX_HTTP_CACHE_ATTEMPTS
116
124
  });
117
125
 
118
126
  return result !== false;
@@ -197,6 +205,7 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
197
205
  body: BodyInit | undefined;
198
206
  warningText: string;
199
207
  readBody: boolean;
208
+ maxAttempts: number;
200
209
  credentialOptions?: CredentialsOptions;
201
210
  }): Promise<Buffer | boolean> {
202
211
  const { terminal, relUrl, method, body, warningText, readBody, credentialOptions } = options;
@@ -227,13 +236,33 @@ export class HttpBuildCacheProvider implements ICloudBuildCacheProvider {
227
236
  });
228
237
 
229
238
  if (!response.ok) {
230
- if (typeof credentials !== 'string' && safeCredentialOptions === CredentialsOptions.Optional) {
231
- // We tried fetching the resource without credentials and that did not work out
232
- // Try again but require credentials this time
233
- // This will trigger the provider to request credentials
239
+ const isNonCredentialResponse = response.status >= 500 && response.status < 600;
240
+
241
+ if (
242
+ !isNonCredentialResponse &&
243
+ typeof credentials !== 'string' &&
244
+ safeCredentialOptions === CredentialsOptions.Optional
245
+ ) {
246
+ // If we don't already have credentials yet, and we got a response from the server
247
+ // that is a "normal" failure (4xx), then we assume that credentials are probably
248
+ // required. Re-attempt the request, requiring credentials this time.
249
+ //
250
+ // This counts as part of the "first attempt", so it is not included in the max attempts
234
251
  return await this._http({ ...options, credentialOptions: CredentialsOptions.Required });
235
252
  }
236
253
 
254
+ if (options.maxAttempts > 1) {
255
+ // Pause a bit before retrying in case the server is busy
256
+ // Add some random jitter to the retry so we can spread out load on the remote service
257
+ // A proper solution might add exponential back off in case the retry count is high (10 or more)
258
+ const factor = 1.0 + Math.random(); // A random number between 1.0 and 2.0
259
+ const retryDelay = Math.floor(factor * this._minHttpRetryDelayMs);
260
+
261
+ await Async.sleep(retryDelay);
262
+
263
+ return await this._http({ ...options, maxAttempts: options.maxAttempts - 1 });
264
+ }
265
+
237
266
  this._reportFailure(terminal, method, response, false, warningText);
238
267
  return false;
239
268
  }
@@ -1,12 +1,9 @@
1
- import { Import } from '@rushstack/node-core-library';
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+
2
4
  import type { IRushPlugin, RushSession, RushConfiguration } from '@rushstack/rush-sdk';
3
5
  import type { HttpBuildCacheProvider, IHttpBuildCacheProviderOptions } from './HttpBuildCacheProvider';
4
6
 
5
- const HttpBuildCacheProviderModule: typeof import('./HttpBuildCacheProvider') = Import.lazy(
6
- './HttpBuildCacheProvider',
7
- require
8
- );
9
-
10
7
  const PLUGIN_NAME: string = 'HttpBuildCachePlugin';
11
8
 
12
9
  /**
@@ -56,31 +53,29 @@ export class RushHttpBuildCachePlugin implements IRushPlugin {
56
53
 
57
54
  public apply(rushSession: RushSession, rushConfig: RushConfiguration): void {
58
55
  rushSession.hooks.initialize.tap(this.pluginName, () => {
59
- rushSession.registerCloudBuildCacheProviderFactory(
60
- 'http',
61
- (buildCacheConfig): HttpBuildCacheProvider => {
62
- const config: IRushHttpBuildCachePluginConfig = (
63
- buildCacheConfig as typeof buildCacheConfig & {
64
- httpConfiguration: IRushHttpBuildCachePluginConfig;
65
- }
66
- ).httpConfiguration;
56
+ rushSession.registerCloudBuildCacheProviderFactory('http', async (buildCacheConfig) => {
57
+ const config: IRushHttpBuildCachePluginConfig = (
58
+ buildCacheConfig as typeof buildCacheConfig & {
59
+ httpConfiguration: IRushHttpBuildCachePluginConfig;
60
+ }
61
+ ).httpConfiguration;
67
62
 
68
- const { url, uploadMethod, headers, tokenHandler, cacheKeyPrefix, isCacheWriteAllowed } = config;
63
+ const { url, uploadMethod, headers, tokenHandler, cacheKeyPrefix, isCacheWriteAllowed } = config;
69
64
 
70
- const options: IHttpBuildCacheProviderOptions = {
71
- pluginName: this.pluginName,
72
- rushProjectRoot: rushConfig.rushJsonFolder,
73
- url: url,
74
- uploadMethod: uploadMethod,
75
- headers: headers,
76
- tokenHandler: tokenHandler,
77
- cacheKeyPrefix: cacheKeyPrefix,
78
- isCacheWriteAllowed: !!isCacheWriteAllowed
79
- };
65
+ const options: IHttpBuildCacheProviderOptions = {
66
+ pluginName: this.pluginName,
67
+ rushProjectRoot: rushConfig.rushJsonFolder,
68
+ url: url,
69
+ uploadMethod: uploadMethod,
70
+ headers: headers,
71
+ tokenHandler: tokenHandler,
72
+ cacheKeyPrefix: cacheKeyPrefix,
73
+ isCacheWriteAllowed: !!isCacheWriteAllowed
74
+ };
80
75
 
81
- return new HttpBuildCacheProviderModule.HttpBuildCacheProvider(options, rushSession);
82
- }
83
- );
76
+ const { HttpBuildCacheProvider } = await import('./HttpBuildCacheProvider');
77
+ return new HttpBuildCacheProvider(options, rushSession);
78
+ });
84
79
  });
85
80
  }
86
81
  }
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+
4
+ jest.mock('node-fetch', function () {
5
+ return Object.assign(jest.fn(), jest.requireActual('node-fetch'));
6
+ });
7
+
8
+ import fetch, { Response } from 'node-fetch';
9
+ import { RushSession, EnvironmentConfiguration } from '@rushstack/rush-sdk';
10
+ import { StringBufferTerminalProvider, Terminal } from '@rushstack/node-core-library';
11
+
12
+ import { HttpBuildCacheProvider } from '../HttpBuildCacheProvider';
13
+
14
+ const EXAMPLE_OPTIONS = {
15
+ url: 'https://buildcache.example.acme.com',
16
+ tokenHandler: {
17
+ exec: 'node',
18
+ args: ['tokenHandler.js']
19
+ },
20
+ uploadMethod: 'POST',
21
+ isCacheWriteAllowed: false,
22
+ pluginName: 'example-plugin',
23
+ rushProjectRoot: '/repo',
24
+ minHttpRetryDelayMs: 1
25
+ };
26
+
27
+ describe('HttpBuildCacheProvider', () => {
28
+ let terminalBuffer: StringBufferTerminalProvider;
29
+ let terminal!: Terminal;
30
+
31
+ beforeEach(() => {
32
+ terminalBuffer = new StringBufferTerminalProvider();
33
+ terminal = new Terminal(terminalBuffer);
34
+ });
35
+
36
+ describe('tryGetCacheEntryBufferByIdAsync', () => {
37
+ it('prints warning if read credentials are not available', async () => {
38
+ jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);
39
+
40
+ const session: RushSession = {} as RushSession;
41
+ const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);
42
+
43
+ mocked(fetch).mockResolvedValue(
44
+ new Response('Unauthorized', {
45
+ status: 401,
46
+ statusText: 'Unauthorized'
47
+ })
48
+ );
49
+
50
+ const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
51
+ expect(result).toBe(undefined);
52
+ expect(fetch).toHaveBeenCalledTimes(1);
53
+ expect(fetch).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {
54
+ body: undefined,
55
+ headers: {},
56
+ method: 'GET',
57
+ redirect: 'follow'
58
+ });
59
+ expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(
60
+ `"Error getting cache entry: Error: Credentials for https://buildcache.example.acme.com/ have not been provided.[n]In CI, verify that RUSH_BUILD_CACHE_CREDENTIAL contains a valid Authorization header value.[n][n]For local developers, run:[n][n] rush update-cloud-credentials --interactive[n][n]"`
61
+ );
62
+ });
63
+
64
+ it('attempts up to 3 times to download a cache entry', async () => {
65
+ jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);
66
+
67
+ const session: RushSession = {} as RushSession;
68
+ const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);
69
+
70
+ mocked(fetch).mockResolvedValueOnce(
71
+ new Response('InternalServiceError', {
72
+ status: 500,
73
+ statusText: 'InternalServiceError'
74
+ })
75
+ );
76
+ mocked(fetch).mockResolvedValueOnce(
77
+ new Response('ServiceUnavailable', {
78
+ status: 503,
79
+ statusText: 'ServiceUnavailable'
80
+ })
81
+ );
82
+ mocked(fetch).mockResolvedValueOnce(
83
+ new Response('BadGateway', {
84
+ status: 504,
85
+ statusText: 'BadGateway'
86
+ })
87
+ );
88
+
89
+ const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
90
+ expect(result).toBe(undefined);
91
+ expect(fetch).toHaveBeenCalledTimes(3);
92
+ expect(fetch).toHaveBeenNthCalledWith(1, 'https://buildcache.example.acme.com/some-key', {
93
+ body: undefined,
94
+ headers: {},
95
+ method: 'GET',
96
+ redirect: 'follow'
97
+ });
98
+ expect(fetch).toHaveBeenNthCalledWith(2, 'https://buildcache.example.acme.com/some-key', {
99
+ body: undefined,
100
+ headers: {},
101
+ method: 'GET',
102
+ redirect: 'follow'
103
+ });
104
+ expect(fetch).toHaveBeenNthCalledWith(3, 'https://buildcache.example.acme.com/some-key', {
105
+ body: undefined,
106
+ headers: {},
107
+ method: 'GET',
108
+ redirect: 'follow'
109
+ });
110
+ expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(
111
+ `"Could not get cache entry: HTTP 504: BadGateway[n]"`
112
+ );
113
+ });
114
+ });
115
+ });