diff --git a/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/package.json b/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/package.json index 685b6c89b5d..897c23faea1 100644 --- a/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/package.json +++ b/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/package.json @@ -11,17 +11,16 @@ "start-proxy-server": "node ./lib/startProxyServer.js" }, "devDependencies": { + "@microsoft/rush-lib": "workspace:*", "@rushstack/heft": "workspace:*", - "local-node-rig": "workspace:*", "@rushstack/rush-amazon-s3-build-cache-plugin": "workspace:*", "@rushstack/node-core-library": "workspace:*", + "@rushstack/terminal": "workspace:*", + "@types/http-proxy": "~1.17.8", "@types/node": "18.17.15", "eslint": "~8.57.0", - "typescript": "~5.4.2", "http-proxy": "~1.18.1", - "@types/http-proxy": "~1.17.8" - }, - "dependencies": { - "@rushstack/terminal": "workspace:*" + "local-node-rig": "workspace:*", + "typescript": "~5.4.2" } } diff --git a/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/src/readObject.ts b/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/src/readObject.ts index aaaa1927cfb..5501dcf6d72 100644 --- a/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/src/readObject.ts +++ b/build-tests/rush-amazon-s3-build-cache-plugin-integration-test/src/readObject.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import { AmazonS3Client } from '@rushstack/rush-amazon-s3-build-cache-plugin'; -import { WebClient } from '@rushstack/rush-amazon-s3-build-cache-plugin'; +import { WebClient } from '@microsoft/rush-lib/lib/utilities/WebClient'; import { ConsoleTerminalProvider, type ITerminal, Terminal } from '@rushstack/terminal'; const webClient: WebClient = new WebClient(); diff --git a/build-tests/rush-lib-declaration-paths-test/config/heft.json b/build-tests/rush-lib-declaration-paths-test/config/heft.json index d3967bd0396..857ca850bcb 100644 --- a/build-tests/rush-lib-declaration-paths-test/config/heft.json +++ b/build-tests/rush-lib-declaration-paths-test/config/heft.json @@ -18,24 +18,8 @@ } }, - "copy-src-typings": { - "taskPlugin": { - "pluginPackage": "@rushstack/heft", - "pluginName": "copy-files-plugin", - "options": { - "copyOperations": [ - { - "sourcePath": "node_modules/@microsoft/rush-lib/src", - "destinationFolders": ["src"], - "includeGlobs": ["npm-check-typings.d.ts"] - } - ] - } - } - }, - "typescript": { - "taskDependencies": ["create-src", "copy-src-typings"] + "taskDependencies": ["create-src"] } } } diff --git a/build-tests/rush-lib-declaration-paths-test/scripts/createSrc.js b/build-tests/rush-lib-declaration-paths-test/scripts/createSrc.js index cf8107f2da0..01bcec568b9 100644 --- a/build-tests/rush-lib-declaration-paths-test/scripts/createSrc.js +++ b/build-tests/rush-lib-declaration-paths-test/scripts/createSrc.js @@ -26,7 +26,7 @@ module.exports = { } } - const indexFileLines = ['/// ', '']; + const indexFileLines = []; for await (const dtsPath of collectDtsPaths(`${rushLibPath}/lib`, '@microsoft/rush-lib/lib')) { indexFileLines.push(`import '${dtsPath}';`); } diff --git a/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-17.json b/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-17.json new file mode 100644 index 00000000000..94b4b46708e --- /dev/null +++ b/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-17.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Remove the `node-fetch` dependency from @rushstack/rush-amazon-s3-build-cache-plugin.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-18.json b/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-18.json new file mode 100644 index 00000000000..318505c1eed --- /dev/null +++ b/common/changes/@microsoft/rush/deduplicate-webclient_2024-12-04-01-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "(BREAKING API CHANGE) Remove the exported `WebClient` API from @rushstack/rush-amazon-s3-build-cache-plugin.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index b3be897bf7c..2ea25f66e04 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -2184,11 +2184,10 @@ importers: version: link:../../libraries/node-core-library ../../../build-tests/rush-amazon-s3-build-cache-plugin-integration-test: - dependencies: - '@rushstack/terminal': - specifier: workspace:* - version: link:../../libraries/terminal devDependencies: + '@microsoft/rush-lib': + specifier: workspace:* + version: link:../../libraries/rush-lib '@rushstack/heft': specifier: workspace:* version: link:../../apps/heft @@ -2198,6 +2197,9 @@ importers: '@rushstack/rush-amazon-s3-build-cache-plugin': specifier: workspace:* version: link:../../rush-plugins/rush-amazon-s3-build-cache-plugin + '@rushstack/terminal': + specifier: workspace:* + version: link:../../libraries/terminal '@types/http-proxy': specifier: ~1.17.8 version: 1.17.14 @@ -3999,9 +4001,6 @@ importers: https-proxy-agent: specifier: ~5.0.0 version: 5.0.1 - node-fetch: - specifier: 2.6.7 - version: 2.6.7 devDependencies: '@microsoft/rush-lib': specifier: workspace:* @@ -4009,9 +4008,6 @@ importers: '@rushstack/heft': specifier: workspace:* version: link:../../apps/heft - '@types/node-fetch': - specifier: 2.6.2 - version: 2.6.2 local-node-rig: specifier: workspace:* version: link:../../rigs/local-node-rig diff --git a/common/reviews/api/rush-amazon-s3-build-cache-plugin.api.md b/common/reviews/api/rush-amazon-s3-build-cache-plugin.api.md index af43e1c9946..e0d5fe032ad 100644 --- a/common/reviews/api/rush-amazon-s3-build-cache-plugin.api.md +++ b/common/reviews/api/rush-amazon-s3-build-cache-plugin.api.md @@ -6,11 +6,11 @@ /// -import * as fetch from 'node-fetch'; import type { IRushPlugin } from '@rushstack/rush-sdk'; import { ITerminal } from '@rushstack/terminal'; import type { RushConfiguration } from '@rushstack/rush-sdk'; import type { RushSession } from '@rushstack/rush-sdk'; +import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient'; // @public export class AmazonS3Client { @@ -61,22 +61,6 @@ export interface IAmazonS3Credentials { sessionToken: string | undefined; } -// Warning: (ae-forgotten-export) The symbol "IWebFetchOptionsBase" needs to be exported by the entry point index.d.ts -// -// @public -export interface IGetFetchOptions extends IWebFetchOptionsBase { - // (undocumented) - verb: 'GET' | never; -} - -// @public -export interface IPutFetchOptions extends IWebFetchOptionsBase { - // (undocumented) - body?: Buffer; - // (undocumented) - verb: 'PUT'; -} - // @public (undocumented) class RushAmazonS3BuildCachePlugin implements IRushPlugin { // (undocumented) @@ -86,30 +70,6 @@ class RushAmazonS3BuildCachePlugin implements IRushPlugin { } export default RushAmazonS3BuildCachePlugin; -// @public -export class WebClient { - constructor(); - // (undocumented) - accept: string | undefined; - // (undocumented) - addBasicAuthHeader(userName: string, password: string): void; - // (undocumented) - fetchAsync(url: string, options?: IGetFetchOptions | IPutFetchOptions): Promise; - // (undocumented) - static mergeHeaders(target: fetch.Headers, source: fetch.Headers): void; - // Warning: (ae-forgotten-export) The symbol "WebClientProxy" needs to be exported by the entry point index.d.ts - // - // (undocumented) - proxy: WebClientProxy; - // (undocumented) - readonly standardHeaders: fetch.Headers; - // (undocumented) - userAgent: string | undefined; -} - -// @public -export type WebClientResponse = fetch.Response; - // (No @packageDocumentation comment for this package) ``` diff --git a/libraries/rush-lib/src/utilities/WebClient.ts b/libraries/rush-lib/src/utilities/WebClient.ts index a2eb11c2e0a..78afdb74f82 100644 --- a/libraries/rush-lib/src/utilities/WebClient.ts +++ b/libraries/rush-lib/src/utilities/WebClient.ts @@ -7,11 +7,6 @@ import * as fetch from 'node-fetch'; import type * as http from 'http'; import { Import } from '@rushstack/node-core-library'; -// =================================================================================================================== -// AS A TEMPORARY WORKAROUND, THIS FILE WAS COPY+PASTED INTO THE "rush-amazon-s3-build-cache-plugin" PROJECT. -// See that copy for notes. -// =================================================================================================================== - const createHttpsProxyAgent: typeof import('https-proxy-agent') = Import.lazy('https-proxy-agent', require); /** @@ -19,13 +14,20 @@ const createHttpsProxyAgent: typeof import('https-proxy-agent') = Import.lazy('h */ export type WebClientResponse = fetch.Response; +/** + * For use with {@link WebClient}. + */ +export type WebClientHeaders = fetch.Headers; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const WebClientHeaders: typeof fetch.Headers = fetch.Headers; + /** * For use with {@link WebClient}. */ export interface IWebFetchOptionsBase { timeoutMs?: number; - verb?: 'GET' | 'PUT'; - headers?: fetch.Headers; + headers?: WebClientHeaders | Record; + redirect?: fetch.RequestInit['redirect']; } /** @@ -38,8 +40,8 @@ export interface IGetFetchOptions extends IWebFetchOptionsBase { /** * For use with {@link WebClient}. */ -export interface IPutFetchOptions extends IWebFetchOptionsBase { - verb: 'PUT'; +export interface IFetchOptionsWithBody extends IWebFetchOptionsBase { + verb: 'PUT' | 'POST' | 'PATCH'; body?: Buffer; } @@ -56,6 +58,8 @@ export enum WebClientProxy { * A helper for issuing HTTP requests. */ export class WebClient { + private static _requestFn: typeof fetch.default = fetch.default; + public readonly standardHeaders: fetch.Headers = new fetch.Headers(); public accept: string | undefined = '*/*'; @@ -63,12 +67,21 @@ export class WebClient { public proxy: WebClientProxy = WebClientProxy.Detect; - public constructor() {} + public static mockRequestFn(fn: typeof fetch.default): void { + WebClient._requestFn = fn; + } + + public static resetMockRequestFn(): void { + WebClient._requestFn = fetch.default; + } - public static mergeHeaders(target: fetch.Headers, source: fetch.Headers): void { - source.forEach((value, name) => { + public static mergeHeaders(target: fetch.Headers, source: fetch.Headers | Record): void { + const iterator: Iterable<[string, string]> = + 'entries' in source && typeof source.entries === 'function' ? source.entries() : Object.entries(source); + + for (const [name, value] of iterator) { target.set(name, value); - }); + } } public addBasicAuthHeader(userName: string, password: string): void { @@ -80,7 +93,7 @@ export class WebClient { public async fetchAsync( url: string, - options?: IGetFetchOptions | IPutFetchOptions + options?: IGetFetchOptions | IFetchOptionsWithBody ): Promise { const headers: fetch.Headers = new fetch.Headers(); @@ -93,6 +106,7 @@ export class WebClient { if (this.userAgent) { headers.set('user-agent', this.userAgent); } + if (this.accept) { headers.set('accept', this.accept); } @@ -126,13 +140,14 @@ export class WebClient { method: options?.verb, headers: headers, agent: agent, - timeout: timeoutMs + timeout: timeoutMs, + redirect: options?.redirect }; - const putOptions: IPutFetchOptions | undefined = options as IPutFetchOptions | undefined; - if (putOptions?.body) { - requestInit.body = putOptions.body; + const optionsWithBody: IFetchOptionsWithBody | undefined = options as IFetchOptionsWithBody | undefined; + if (optionsWithBody?.body) { + requestInit.body = optionsWithBody.body; } - return await fetch.default(url, requestInit); + return await WebClient._requestFn(url, requestInit); } } diff --git a/libraries/rush-lib/src/utilities/test/WebClient.test.ts b/libraries/rush-lib/src/utilities/test/WebClient.test.ts new file mode 100644 index 00000000000..ea968a7394c --- /dev/null +++ b/libraries/rush-lib/src/utilities/test/WebClient.test.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { WebClient, WebClientHeaders } from '../WebClient'; + +describe(WebClient.name, () => { + describe(WebClient.mergeHeaders.name, () => { + it('should merge headers', () => { + const target: WebClientHeaders = new WebClientHeaders({ header1: 'value1' }); + const source: WebClientHeaders = new WebClientHeaders({ header2: 'value2' }); + + WebClient.mergeHeaders(target, source); + expect(target.raw()).toMatchInlineSnapshot(` +Object { + "header1": Array [ + "value1", + ], + "header2": Array [ + "value2", + ], +} +`); + }); + + it('should handle an empty source', () => { + const target: WebClientHeaders = new WebClientHeaders({ header1: 'value1' }); + const source: WebClientHeaders = new WebClientHeaders(); + + WebClient.mergeHeaders(target, source); + expect(target.raw()).toMatchInlineSnapshot(` +Object { + "header1": Array [ + "value1", + ], +} +`); + }); + + it('should handle an empty target', () => { + const target: WebClientHeaders = new WebClientHeaders(); + const source: WebClientHeaders = new WebClientHeaders({ header2: 'value2' }); + + WebClient.mergeHeaders(target, source); + expect(target.raw()).toMatchInlineSnapshot(` +Object { + "header2": Array [ + "value2", + ], +} +`); + }); + + it('should handle both empty', () => { + const target: WebClientHeaders = new WebClientHeaders(); + const source: WebClientHeaders = new WebClientHeaders(); + + WebClient.mergeHeaders(target, source); + expect(target.raw()).toMatchInlineSnapshot(`Object {}`); + }); + + it('should handle overwriting values', () => { + const target: WebClientHeaders = new WebClientHeaders({ header1: 'value1' }); + const source: WebClientHeaders = new WebClientHeaders({ header1: 'value2' }); + + WebClient.mergeHeaders(target, source); + expect(target.raw()).toMatchInlineSnapshot(` +Object { + "header1": Array [ + "value2", + ], +} +`); + }); + + it('should handle a JS object as the source', () => { + const target: WebClientHeaders = new WebClientHeaders({ header1: 'value1' }); + + WebClient.mergeHeaders(target, { header2: 'value2' }); + expect(target.raw()).toMatchInlineSnapshot(` +Object { + "header1": Array [ + "value1", + ], + "header2": Array [ + "value2", + ], +} +`); + }); + }); +}); diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json index 32e2b096d48..4c93e5e2fd6 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json @@ -22,13 +22,11 @@ "@rushstack/node-core-library": "workspace:*", "@rushstack/rush-sdk": "workspace:*", "@rushstack/terminal": "workspace:*", - "https-proxy-agent": "~5.0.0", - "node-fetch": "2.6.7" + "https-proxy-agent": "~5.0.0" }, "devDependencies": { "@microsoft/rush-lib": "workspace:*", "@rushstack/heft": "workspace:*", - "local-node-rig": "workspace:*", - "@types/node-fetch": "2.6.2" + "local-node-rig": "workspace:*" } } diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts index d040e8d0b75..2d4292bd8af 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts @@ -11,9 +11,9 @@ import { EnvironmentVariableNames, EnvironmentConfiguration } from '@rushstack/rush-sdk'; +import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient'; import { AmazonS3Client } from './AmazonS3Client'; -import { WebClient } from './WebClient'; import { type IAmazonS3Credentials, fromAmazonEnv, fromRushEnv } from './AmazonS3Credentials'; /** diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3Client.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3Client.ts index 1dcfab26e3a..2894235df0c 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3Client.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3Client.ts @@ -4,10 +4,15 @@ import { Async } from '@rushstack/node-core-library'; import { Colorize, type ITerminal } from '@rushstack/terminal'; import * as crypto from 'crypto'; -import * as fetch from 'node-fetch'; +import { + type IGetFetchOptions, + type IFetchOptionsWithBody, + type WebClientResponse, + type WebClient, + WebClientHeaders +} from '@rushstack/rush-sdk/lib/utilities/WebClient'; import type { IAmazonS3BuildCacheProviderOptionsAdvanced } from './AmazonS3BuildCacheProvider'; -import type { IGetFetchOptions, IPutFetchOptions, WebClient } from './WebClient'; import { type IAmazonS3Credentials, fromRushEnv } from './AmazonS3Credentials'; const CONTENT_HASH_HEADER_NAME: 'x-amz-content-sha256' = 'x-amz-content-sha256'; @@ -113,7 +118,7 @@ export class AmazonS3Client { public async getObjectAsync(objectName: string): Promise { this._writeDebugLine('Reading object from S3'); return await this._sendCacheRequestWithRetriesAsync(async () => { - const response: fetch.Response = await this._makeRequestAsync('GET', objectName); + const response: WebClientResponse = await this._makeRequestAsync('GET', objectName); if (response.ok) { return { hasNetworkError: false, @@ -158,7 +163,7 @@ export class AmazonS3Client { } await this._sendCacheRequestWithRetriesAsync(async () => { - const response: fetch.Response = await this._makeRequestAsync('PUT', objectName, objectBuffer); + const response: WebClientResponse = await this._makeRequestAsync('PUT', objectName, objectBuffer); if (!response.ok) { return { hasNetworkError: true, @@ -194,10 +199,10 @@ export class AmazonS3Client { verb: 'GET' | 'PUT', objectName: string, body?: Buffer - ): Promise { + ): Promise { const isoDateString: IIsoDateString = this._getIsoDateString(); const bodyHash: string = this._getSha256(body); - const headers: fetch.Headers = new fetch.Headers(); + const headers: WebClientHeaders = new WebClientHeaders(); headers.set(DATE_HEADER_NAME, isoDateString.dateTime); headers.set(CONTENT_HASH_HEADER_NAME, bodyHash); @@ -293,12 +298,12 @@ export class AmazonS3Client { } } - const webFetchOptions: IGetFetchOptions | IPutFetchOptions = { + const webFetchOptions: IGetFetchOptions | IFetchOptionsWithBody = { verb, headers }; if (verb === 'PUT') { - (webFetchOptions as IPutFetchOptions).body = body; + (webFetchOptions as IFetchOptionsWithBody).body = body; } const url: string = `${this._s3Endpoint}${canonicalUri}`; @@ -310,7 +315,7 @@ export class AmazonS3Client { this._writeDebugLine(Colorize.cyan(`\t${name}: ${value}`)); }); - const response: fetch.Response = await this._webClient.fetchAsync(url, webFetchOptions); + const response: WebClientResponse = await this._webClient.fetchAsync(url, webFetchOptions); return response; } @@ -351,7 +356,7 @@ export class AmazonS3Client { }; } - private async _safeReadResponseTextAsync(response: fetch.Response): Promise { + private async _safeReadResponseTextAsync(response: WebClientResponse): Promise { try { return await response.text(); } catch (err) { @@ -360,7 +365,7 @@ export class AmazonS3Client { return undefined; } - private async _getS3ErrorAsync(response: fetch.Response): Promise { + private async _getS3ErrorAsync(response: WebClientResponse): Promise { const text: string | undefined = await this._safeReadResponseTextAsync(response); return new Error( `Amazon S3 responded with status code ${response.status} (${response.statusText})${ diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/WebClient.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/WebClient.ts deleted file mode 100644 index 7c1776d998c..00000000000 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/WebClient.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -// =================================================================================================================== -// AS A TEMPORARY WORKAROUND, THIS FILE WAS COPY+PASTED FROM THE "rush-lib" PROJECT. -// -// Eventually we plan to convert it into a more generic API for "node-core-library" or -// else replace it with a third party solution such as Axios. See the discussion here: -// https://github.com/microsoft/rushstack/pull/3036#discussion_r758010126 -// =================================================================================================================== - -import * as os from 'os'; -import * as process from 'process'; -import * as fetch from 'node-fetch'; -import type * as http from 'http'; -import { Import } from '@rushstack/node-core-library'; - -const createHttpsProxyAgent: typeof import('https-proxy-agent') = Import.lazy('https-proxy-agent', require); - -/** - * For use with {@link WebClient}. - * - * @public - */ -export type WebClientResponse = fetch.Response; - -/** - * For use with {@link WebClient}. - * - * @public - */ -export interface IWebFetchOptionsBase { - timeoutMs?: number; - verb?: 'GET' | 'PUT'; - headers?: fetch.Headers; -} - -/** - * For use with {@link WebClient}. - * - * @public - */ -export interface IGetFetchOptions extends IWebFetchOptionsBase { - verb: 'GET' | never; -} - -/** - * For use with {@link WebClient}. - * - * @public - */ -export interface IPutFetchOptions extends IWebFetchOptionsBase { - verb: 'PUT'; - body?: Buffer; -} - -/** - * For use with {@link WebClient}. - * @public - */ -export enum WebClientProxy { - None, - Detect, - Fiddler -} - -/** - * A helper for issuing HTTP requests. - * - * @public - */ -export class WebClient { - public readonly standardHeaders: fetch.Headers = new fetch.Headers(); - - public accept: string | undefined = '*/*'; - public userAgent: string | undefined = `rush node/${process.version} ${os.platform()} ${os.arch()}`; - - public proxy: WebClientProxy = WebClientProxy.Detect; - - public constructor() {} - - public static mergeHeaders(target: fetch.Headers, source: fetch.Headers): void { - source.forEach((value, name) => { - target.set(name, value); - }); - } - - public addBasicAuthHeader(userName: string, password: string): void { - this.standardHeaders.set( - 'Authorization', - 'Basic ' + Buffer.from(userName + ':' + password).toString('base64') - ); - } - - public async fetchAsync( - url: string, - options?: IGetFetchOptions | IPutFetchOptions - ): Promise { - const headers: fetch.Headers = new fetch.Headers(); - - WebClient.mergeHeaders(headers, this.standardHeaders); - - if (options?.headers) { - WebClient.mergeHeaders(headers, options.headers); - } - - if (this.userAgent) { - headers.set('user-agent', this.userAgent); - } - if (this.accept) { - headers.set('accept', this.accept); - } - - let proxyUrl: string = ''; - - switch (this.proxy) { - case WebClientProxy.Detect: - if (process.env.HTTPS_PROXY) { - proxyUrl = process.env.HTTPS_PROXY; - } else if (process.env.HTTP_PROXY) { - proxyUrl = process.env.HTTP_PROXY; - } - break; - - case WebClientProxy.Fiddler: - // For debugging, disable cert validation - // eslint-disable-next-line - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - proxyUrl = 'http://localhost:8888/'; - break; - } - - let agent: http.Agent | undefined = undefined; - if (proxyUrl) { - agent = createHttpsProxyAgent(proxyUrl); - } - - const timeoutMs: number = options?.timeoutMs !== undefined ? options.timeoutMs : 15 * 1000; // 15 seconds - const requestInit: fetch.RequestInit = { - method: options?.verb, - headers: headers, - agent: agent, - timeout: timeoutMs - }; - const putOptions: IPutFetchOptions | undefined = options as IPutFetchOptions | undefined; - if (putOptions?.body) { - requestInit.body = putOptions.body; - } - - return await fetch.default(url, requestInit); - } -} diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/index.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/index.ts index dbb5a702b31..b110cb1a855 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/index.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/index.ts @@ -5,7 +5,6 @@ import { RushAmazonS3BuildCachePlugin } from './RushAmazonS3BuildCachePlugin'; export { type IAmazonS3Credentials } from './AmazonS3Credentials'; export { AmazonS3Client } from './AmazonS3Client'; -export { WebClient, type IGetFetchOptions, type IPutFetchOptions, type WebClientResponse } from './WebClient'; export default RushAmazonS3BuildCachePlugin; export type { IAmazonS3BuildCacheProviderOptionsBase, diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts index 4ab30117f11..f66b89415ed 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +jest.mock('@rushstack/rush-sdk/lib/utilities/WebClient', () => { + return jest.requireActual('@microsoft/rush-lib/lib/utilities/WebClient'); +}); + import { ConsoleTerminalProvider, StringBufferTerminalProvider, Terminal } from '@rushstack/terminal'; import { RushSession, diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3Client.test.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3Client.test.ts index d87115b17ed..1cdedf55015 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3Client.test.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3Client.test.ts @@ -1,12 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +jest.mock('@rushstack/rush-sdk/lib/utilities/WebClient', () => { + return jest.requireActual('@microsoft/rush-lib/lib/utilities/WebClient'); +}); + import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; -import { Response, type ResponseInit } from 'node-fetch'; +import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient'; import type { IAmazonS3BuildCacheProviderOptionsAdvanced } from '../AmazonS3BuildCacheProvider'; import { AmazonS3Client } from '../AmazonS3Client'; -import { WebClient } from '../WebClient'; import type { IAmazonS3Credentials } from '../AmazonS3Credentials'; const webClient = new WebClient(); @@ -219,7 +222,8 @@ describe(AmazonS3Client.name, () => { describe('Making requests', () => { interface IResponseOptions { body?: string; - responseInit: ResponseInit; + status: number; + statusText?: string; } let realDate: typeof Date; @@ -249,9 +253,14 @@ describe(AmazonS3Client.name, () => { response: IResponseOptions, testOptions: ITestOptions ): Promise { - const spy: jest.SpyInstance = jest - .spyOn(WebClient.prototype, 'fetchAsync') - .mockReturnValue(Promise.resolve(new Response(response.body, response.responseInit))); + const spy: jest.SpyInstance = jest.spyOn(WebClient.prototype, 'fetchAsync').mockReturnValue( + Promise.resolve({ + buffer: response.body ? async () => Buffer.from(response.body || '') : undefined, + status: response.status, + statusText: response.statusText, + ok: response.status >= 200 && response.status < 300 + }) as unknown as ReturnType + ); const s3Client: AmazonS3Client = new AmazonS3Client(credentials, options, webClient, terminal); let result: TResponse; @@ -321,9 +330,7 @@ describe(AmazonS3Client.name, () => { 'abc123', { body: expectedContents, - responseInit: { - status: 200 - } + status: 200 }, { shouldRetry: false @@ -342,9 +349,7 @@ describe(AmazonS3Client.name, () => { 'abc123', { body: expectedContents, - responseInit: { - status: 200 - } + status: 200 }, { shouldRetry: false } ); @@ -358,10 +363,8 @@ describe(AmazonS3Client.name, () => { DUMMY_OPTIONS, 'abc123', { - responseInit: { - status: 404, - statusText: 'Not Found' - } + status: 404, + statusText: 'Not Found' }, { shouldRetry: false @@ -379,10 +382,8 @@ describe(AmazonS3Client.name, () => { DUMMY_OPTIONS, 'abc123', { - responseInit: { - status: 500, - statusText: 'Server Error' - } + status: 500, + statusText: 'Server Error' }, { shouldRetry: true @@ -407,10 +408,8 @@ describe(AmazonS3Client.name, () => { DUMMY_OPTIONS, 'abc123', { - responseInit: { - status: 400, - statusText: 'Bad Request' - } + status: 400, + statusText: 'Bad Request' }, { shouldRetry: false @@ -431,10 +430,8 @@ describe(AmazonS3Client.name, () => { DUMMY_OPTIONS, 'abc123', { - responseInit: { - status: 401, - statusText: 'Unauthorized' - } + status: 401, + statusText: 'Unauthorized' }, { shouldRetry: false @@ -465,10 +462,8 @@ describe(AmazonS3Client.name, () => { return await s3Client.getObjectAsync('abc123'); }, { - responseInit: { - status: code, - statusText: 'Unauthorized' - } + status: code, + statusText: 'Unauthorized' }, { shouldRetry: false @@ -497,10 +492,8 @@ describe(AmazonS3Client.name, () => { DUMMY_OPTIONS, 'abc123', { - responseInit: { - status: 403, - statusText: 'Unauthorized' - } + status: 403, + statusText: 'Unauthorized' }, { shouldRetry: false @@ -564,9 +557,7 @@ describe(AmazonS3Client.name, () => { 'abc123', 'abc123-contents', { - responseInit: { - status: 200 - } + status: 200 }, { shouldRetry: false } ); @@ -579,9 +570,7 @@ describe(AmazonS3Client.name, () => { 'abc123', 'abc123-contents', { - responseInit: { - status: 200 - } + status: 200 }, { shouldRetry: false } ); @@ -596,10 +585,8 @@ describe(AmazonS3Client.name, () => { 'abc123', 'abc123-contents', { - responseInit: { - status: 500, - statusText: 'Server Error' - } + status: 500, + statusText: 'Server Error' }, { shouldRetry: true } ) diff --git a/webpack/webpack-deep-imports-plugin/src/DeepImportsPlugin.ts b/webpack/webpack-deep-imports-plugin/src/DeepImportsPlugin.ts index 223bc40b9d9..548e6977dcb 100644 --- a/webpack/webpack-deep-imports-plugin/src/DeepImportsPlugin.ts +++ b/webpack/webpack-deep-imports-plugin/src/DeepImportsPlugin.ts @@ -144,6 +144,7 @@ export class DeepImportsPlugin extends DllPlugin { interface ILibModuleDescriptor { libPathWithoutExtension: string; moduleId: string | number | null; + secondaryChunkId: string | undefined; } const pathsToIgnoreWithoutExtension: Set = this._pathsToIgnoreWithoutExtensions; @@ -152,8 +153,8 @@ export class DeepImportsPlugin extends DllPlugin { const encounteredLibPaths: Set = new Set(); for (const runtimeChunk of runtimeChunks) { const libModules: ILibModuleDescriptor[] = []; - for (const initialChunk of runtimeChunk.getAllInitialChunks()) { - for (const runtimeChunkModule of compilation.chunkGraph.getChunkModules(initialChunk)) { + function processChunks(chunk: Chunk, secondaryChunkId: string | undefined): void { + for (const runtimeChunkModule of compilation.chunkGraph.getChunkModules(chunk)) { if (runtimeChunkModule.type === 'javascript/auto') { const modulePath: string | undefined = (runtimeChunkModule as NormalModule)?.resource; if (modulePath?.startsWith(resolvedLibInFolder) && modulePath.endsWith(JS_EXTENSION)) { @@ -166,7 +167,8 @@ export class DeepImportsPlugin extends DllPlugin { if (!encounteredLibPaths.has(relativePathWithoutExtension)) { libModules.push({ libPathWithoutExtension: relativePathWithoutExtension, - moduleId: compilation.chunkGraph.getModuleId(runtimeChunkModule) + moduleId: compilation.chunkGraph.getModuleId(runtimeChunkModule), + secondaryChunkId }); encounteredLibPaths.add(relativePathWithoutExtension); @@ -177,6 +179,16 @@ export class DeepImportsPlugin extends DllPlugin { } } + for (const initialChunk of runtimeChunk.getAllInitialChunks()) { + processChunks(initialChunk, undefined); + } + + for (const secondaryChunk of runtimeChunk.getAllAsyncChunks()) { + if (secondaryChunk.id) { + processChunks(secondaryChunk, String(secondaryChunk.id)); + } + } + libModulesByChunk.set(runtimeChunk, libModules); } @@ -219,12 +231,22 @@ export class DeepImportsPlugin extends DllPlugin { await Async.forEachAsync( libModules, - async ({ libPathWithoutExtension, moduleId }) => { + async ({ libPathWithoutExtension, moduleId, secondaryChunkId }) => { const depth: number = countSlashes(libPathWithoutExtension); const requirePath: string = '../'.repeat(depth) + libOutFolderRelativeOutputPath; - const moduleText: string = [ - `module.exports = require(${JSON.stringify(requirePath)})(${JSON.stringify(moduleId)});` - ].join('\n'); + let moduleText: string; + if (secondaryChunkId) { + moduleText = [ + `const runtimeChunkRequire = require(${JSON.stringify(requirePath)});`, + `// Ensure the chunk containing the module is loaded`, + `runtimeChunkRequire.f.require(${JSON.stringify(secondaryChunkId)});`, + `module.exports = runtimeChunkRequire(${JSON.stringify(moduleId)});` + ].join('\n'); + } else { + moduleText = [ + `module.exports = require(${JSON.stringify(requirePath)})(${JSON.stringify(moduleId)});` + ].join('\n'); + } compilation.emitAsset( `${outputPathRelativeLibOutFolder}/${libPathWithoutExtension}${JS_EXTENSION}`,