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}`,