Skip to content

Commit f29d18d

Browse files
Copiloticlantonclaude
authored
Add file-based transfer APIs to build cache provider interface and all cache plugins (#5746)
* feat: add streaming support to build cache provider interface and HTTP plugin Add optional tryGetCacheEntryStreamByIdAsync and trySetCacheEntryStreamAsync methods to ICloudBuildCacheProvider. Implement streaming in HttpBuildCacheProvider and update OperationBuildCache to prefer streaming when available. Add fetchStreamAsync to WebClient and stream write support to FileSystemBuildCacheProvider. Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/050e10a7-3cad-4da4-93e5-9941453283b9 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * fix lint warnings, add change files and API report update Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/050e10a7-3cad-4da4-93e5-9941453283b9 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * address code review feedback: improve type annotations and JSDoc Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/050e10a7-3cad-4da4-93e5-9941453283b9 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Replace manual change files with rush change --bulk generated change file Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/798065bf-040f-462e-8ea9-b9ca5659196d Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Use createReadStream/createWriteStream from FileSystem instead of fs directly Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/5ad20d4d-c9a4-4855-bb13-8dd9e2c1350b Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Use ensureFolderExists option in FileSystem.createWriteStream Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/199d4b3e-1f3f-44e1-9fc6-7b4a0e027c7e Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Add streaming support to Amazon S3 and Azure Storage build cache plugins Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/6212baeb-266c-4823-94df-251c69a8f74c Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Address code review: add documentation for type assertion and no-retry streaming upload Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/6212baeb-266c-4823-94df-251c69a8f74c Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Gate streaming build cache behind useStreamingBuildCache experiment flag Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/63fb5abe-e500-4a9c-bd82-3ed613989ef4 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Update changelogs. Co-authored-by: Ian Clanton-Thuon <iclanton@users.noreply.github.com> * Expand FileSystem.createWriteStream. * Use destructuring on rushConfiguration in PhasedScriptAction. * Clean up some duplicated types. * Fix CI: add missing useStreamingBuildCache to bridge plugin, fix WebClient private member type mismatch Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/1b32730c-9ce0-4e75-a2e1-45f06a628960 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * DRY up duplicated code across WebClient, AmazonS3Client, and HttpBuildCacheProvider Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/c103fb17-b8b1-4d02-88ba-9db236e9f48f Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Address code review: use Buffer.isBuffer for clearer body length check Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/c103fb17-b8b1-4d02-88ba-9db236e9f48f Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Change streaming cache APIs from Readable to NodeJS.ReadableStream Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/66d5d3e1-e14b-41bb-9382-46fc4176f977 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * DRY up Azure/HTTP cache providers, fix bridge plugin experiment config Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/b57084f4-36d7-42f3-8876-7298756b887e Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Clean up FileSystemBuildCacheProvider. * fixup! Fix CI: add missing useStreamingBuildCache to bridge plugin, fix WebClient private member type mismatch * Clean up WebClient. * Handle Content-Encoding decompression in streaming WebClient path Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/13978d72-ea9a-4463-a6a2-51ecfc72f3ff Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Fix error message to show specific unsupported encoding value Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/13978d72-ea9a-4463-a6a2-51ecfc72f3ff Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Fix CI: Move WebClient private members to module-level for rush-sdk type compatibility Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/d8f5e137-3cb0-4ed9-8391-50d7a33d804e Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Address review comments: fix response.resume race, stream retry bug, error logging, stream cleanup, and nits Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/d0190bf7-a346-4a71-93bf-d5375d98b552 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Add unit tests for streaming cache APIs and fill buffer-based test gaps Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/86050f65-dd6c-45f4-ac41-95fdb860c053 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Fix buffer path error message to show specific unsupported encoding value The streaming path was fixed in 8becd08 but the buffer path still printed the entire encodings array instead of the individual value. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Extract _getObjectName and _validateWriteAllowed helpers in S3 provider Deduplicate the S3 prefix logic (repeated 4 times) into a helper, and extract the write-permission guard (repeated in buffer and stream set methods) to match the HTTP provider's pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Extract _getContentEncodings helper to deduplicate encoding parsing Both the buffer and streaming response paths duplicated the same Content-Encoding header parsing logic. Extract into a shared helper that returns a parsed string array or undefined. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Scope cacheEntryBuffer to its branch, use cloudCacheHit flag Replace the outer-scoped cacheEntryBuffer (which was only used as a boolean flag at the cache-miss check) with an explicit cloudCacheHit boolean. This scopes the buffer into the else branch and makes the streaming path set the flag consistently too. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Reuse a single WebClient instance in HttpBuildCacheProvider A new WebClient was being constructed on every call to _makeHttpCoreRequestAsync. Since the provider never configures any WebClient instance state, a single class member suffices. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use property shorthand in HttpBuildCacheProvider Replace verbose `terminal: terminal`, `headers: headers`, `body: body`, and `credential: credential` with ES6 shorthand property syntax. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix unit test issues in streaming cache tests - Fix credential fallback test: mock CredentialCache so cached credentials are available, making the test actually validate the stream-body guard (previously, the test passed trivially because _tryGetCredentialsAsync would throw before making a second request) - Fix 504 statusText from 'BadGateway' to 'Gateway Timeout' - Replace fragile S3 upload snapshot that captured Readable internals (breaks on Node.js upgrades) with targeted assertions on URL, verb, headers, and body identity - Replace fail() + try/catch with expect().rejects.toThrow() - Move Readable import to top of S3 test file instead of per-test dynamic import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rework streaming cache APIs to file-based APIs with S3 payload signing Replace stream-based ICloudBuildCacheProvider methods with file-path-based alternatives that give providers full control over I/O: - tryGetCacheEntryStreamByIdAsync → tryGetCacheEntryToFileAsync - trySetCacheEntryStreamAsync → trySetCacheEntryFromFileAsync Key improvements: - S3 uploads now hash the tarball on disk before streaming, restoring AWS Signature V4 payload signing (removes UNSIGNED_PAYLOAD) - Azure provider uses SDK-native uploadFile/downloadToFile - HTTP provider uses FileSystem.createReadStream/createWriteStreamAsync - Providers that don't need pre-upload computation (HTTP, Azure) don't pay the cost of hashing - Experiment renamed to useDirectFileTransfersForBuildCache Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rename file-based cache APIs to tryDownload/tryUpload for clarity - tryGetCacheEntryToFileAsync → tryDownloadCacheEntryToFileAsync - trySetCacheEntryFromFileAsync → tryUploadCacheEntryFromFileAsync Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace jest.mock('node:fs') with FileSystem spies in cache tests Mock FileSystem.createReadStream, createWriteStreamAsync, and ensureFolderAsync via jest.spyOn instead of module-level fs mocks. This is more targeted, less brittle, and consistent with the production code's use of the FileSystem abstraction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Clean up partial file on failed cache download If tryDownloadCacheEntryToFileAsync throws mid-download, a corrupt partial file could be left at the target path. On the next build, tryGetCacheEntryPathByIdAsync would find it and try to untar it. Delete the file in the catch block to prevent this. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Ensure parent directory exists before Azure downloadToFile The HTTP and S3 providers both ensure the parent directory exists before writing the cache entry file. The Azure provider was missing this, which would cause failures on a fresh machine where the build cache folder hasn't been created yet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fixup! Ensure parent directory exists before Azure downloadToFile * Update change file descriptions, Azure JSDoc, and test names - Rush change file: replace "streaming APIs" with file-based transfer API names and mention the experiment flag - node-core-library change file: fix broken backtick formatting - Azure provider: update JSDoc from "stream" to "file-based" - HTTP test names: replace "stream consumed" / "stream bodies" with file-based language Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix S3 retry delay log unit from seconds to milliseconds The delay variable is in milliseconds but the log message said "s". This produced misleading output like "Will retry request in 4000s...". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fixup! Rework streaming cache APIs to file-based APIs with S3 payload signing Remove unused onBlobAlreadyExists parameter from _trySetBlobDataAsync This callback was previously used to drain incoming streams when the blob already existed. With the switch to file-based APIs, no callers pass this parameter anymore. * Add missing test coverage for file-based cache APIs - HTTP: add 404 cache miss test for tryDownloadCacheEntryToFileAsync - HTTP: add pipeline assertion in download success test - S3: add retry test for downloadObjectToFileAsync on transient 5xx - S3: add pipeline assertions in download success/miss tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix "unknown bytes" debug log wording in HttpBuildCacheProvider For stream-body requests, the log read "unknown bytes" which is awkward. Change to "unknown length" for clarity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address dmichon-msft review comments - Add clarifying comment on maxAttempts: 1 for uploads explaining why the parameter exists (shared between download with retries and upload without) - Replace S3 download snapshot containing auth headers with explicit field assertions, avoiding credential-looking strings in snapshots - Update inline snapshot for "unknown length" wording change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: clean up partial files when tryDownloadCacheEntryToFileAsync returns false Providers may catch errors internally and return false instead of throwing, leaving a partially written file on disk. Move the cleanup logic outside the catch block so it runs for any non-success case, preventing corrupted partial files from being treated as valid cache entries on subsequent builds. * fix: merge main and update test to use jest.mocked() * Restructure S3 response handling and pre-resolve credentials for stream uploads * Destructure response variable in _handleGetResponseAsync * docs: clarify build cache direct file transfer experiment * Refine streaming cache upload handling * Fix unresolved streaming cache review comments * Fix atomic cloud cache download writes --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> Co-authored-by: Ian Clanton-Thuon <iclanton@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6bb6a2b commit f29d18d

25 files changed

Lines changed: 1993 additions & 423 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add optional file-based transfer APIs (`tryDownloadCacheEntryToFileAsync`, `tryUploadCacheEntryFromFileAsync`) to `ICloudBuildCacheProvider`, allowing cache plugins to transfer cache entries directly to and from files on disk without buffering entire contents in memory. Implement in `@rushstack/rush-http-build-cache-plugin`, `@rushstack/rush-amazon-s3-build-cache-plugin`, and `@rushstack/rush-azure-storage-build-cache-plugin`. Gated behind the `useDirectFileTransfersForBuildCache` experiment.",
5+
"type": "none",
6+
"packageName": "@microsoft/rush"
7+
}
8+
],
9+
"packageName": "@microsoft/rush",
10+
"email": "198982749+Copilot@users.noreply.github.com"
11+
}

common/reviews/api/rush-amazon-s3-build-cache-plugin.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient';
1515
// @public
1616
export class AmazonS3Client {
1717
constructor(credentials: IAmazonS3Credentials | undefined, options: IAmazonS3BuildCacheProviderOptionsAdvanced, webClient: WebClient, terminal: ITerminal);
18+
downloadObjectToFileAsync(objectName: string, localFilePath: string): Promise<boolean>;
1819
// (undocumented)
1920
getObjectAsync(objectName: string): Promise<Buffer | undefined>;
2021
// (undocumented)
@@ -25,6 +26,7 @@ export class AmazonS3Client {
2526
static tryDeserializeCredentials(credentialString: string | undefined): IAmazonS3Credentials | undefined;
2627
// (undocumented)
2728
uploadObjectAsync(objectName: string, objectBuffer: Buffer): Promise<void>;
29+
uploadObjectFromFileAsync(objectName: string, localFilePath: string): Promise<void>;
2830
// (undocumented)
2931
static UriEncode(input: string): string;
3032
}

common/reviews/api/rush-lib.api.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ export class ExperimentsConfiguration {
313313
// @beta
314314
export class FileSystemBuildCacheProvider {
315315
constructor(options: IFileSystemBuildCacheProviderOptions);
316-
getCacheEntryPath(cacheId: string): string;
316+
readonly getCacheEntryPath: (cacheId: string) => string;
317317
tryGetCacheEntryPathByIdAsync(terminal: ITerminal, cacheId: string): Promise<string | undefined>;
318318
trySetCacheEntryBufferAsync(terminal: ITerminal, cacheId: string, entryBuffer: Buffer): Promise<string>;
319319
}
@@ -354,10 +354,12 @@ export interface ICloudBuildCacheProvider {
354354
deleteCachedCredentialsAsync(terminal: ITerminal): Promise<void>;
355355
// (undocumented)
356356
readonly isCacheWriteAllowed: boolean;
357+
tryDownloadCacheEntryToFileAsync?(terminal: ITerminal, cacheId: string, localFilePath: string): Promise<boolean>;
357358
// (undocumented)
358359
tryGetCacheEntryBufferByIdAsync(terminal: ITerminal, cacheId: string): Promise<Buffer | undefined>;
359360
// (undocumented)
360361
trySetCacheEntryBufferAsync(terminal: ITerminal, cacheId: string, entryBuffer: Buffer): Promise<boolean>;
362+
tryUploadCacheEntryFromFileAsync?(terminal: ITerminal, cacheId: string, localFilePath: string): Promise<boolean>;
361363
// (undocumented)
362364
updateCachedCredentialAsync(terminal: ITerminal, credential: string): Promise<void>;
363365
// (undocumented)
@@ -487,6 +489,7 @@ export interface IExperimentsJson {
487489
printEventHooksOutputToConsole?: boolean;
488490
rushAlerts?: boolean;
489491
strictChangefileValidation?: boolean;
492+
useDirectFileTransfersForBuildCache?: boolean;
490493
useIPCScriptsInWatchMode?: boolean;
491494
usePnpmFrozenLockfileForRushInstall?: boolean;
492495
usePnpmLockfileOnlyThenFrozenLockfileForRushUpdate?: boolean;
@@ -600,6 +603,7 @@ export interface _IOperationBuildCacheOptions {
600603
buildCacheConfiguration: BuildCacheConfiguration;
601604
excludeAppleDoubleFiles: boolean;
602605
terminal: ITerminal;
606+
useDirectFileTransfersForBuildCache: boolean;
603607
}
604608

605609
// @alpha

libraries/rush-lib/src/api/ExperimentsConfiguration.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@ export interface IExperimentsJson {
144144
* policy's main project.
145145
*/
146146
strictChangefileValidation?: boolean;
147+
148+
/**
149+
* If true, the build cache will use file-based APIs to transfer cache entries to and from cloud
150+
* storage. This avoids loading the entire cache entry into memory, which can prevent out-of-memory
151+
* errors for large build outputs and allow cache entries to exceed the limit of a single Buffer.
152+
* The cloud cache provider plugin must implement the optional file-based methods for this to take
153+
* effect; otherwise it falls back to the buffer-based approach.
154+
*/
155+
useDirectFileTransfersForBuildCache?: boolean;
147156
}
148157

149158
const _EXPERIMENTS_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);

libraries/rush-lib/src/cli/RushCommandLineParser.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ export class RushCommandLineParser extends CommandLineParser {
221221
}
222222
}
223223

224-
const quietModeValue: string | undefined =
225-
process.env[EnvironmentVariableNames.RUSH_QUIET_MODE];
224+
const quietModeValue: string | undefined = process.env[EnvironmentVariableNames.RUSH_QUIET_MODE];
226225
if (quietModeValue === '1' || quietModeValue === 'true') {
227226
return true;
228227
}

libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
308308
// Initialize the stopwatch's start time at 0 (process startup).
309309
const stopwatch: Stopwatch = Stopwatch.start(0);
310310

311+
const {
312+
defaultSubspace,
313+
subspacesFeatureEnabled,
314+
pnpmOptions: { useWorkspaces }
315+
} = this.rushConfiguration;
311316
if (this._alwaysInstall || this._installParameter?.value) {
312317
await measureAsyncFn(`${PERF_PREFIX}:install`, async () => {
313318
const { doBasicInstallAsync } = await import(
@@ -331,7 +336,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
331336
afterInstallAsync: (subspace: Subspace) =>
332337
this.rushSession.hooks.afterInstall.promise(this, subspace, variant),
333338
// Eventually we may want to allow a subspace to be selected here
334-
subspace: this.rushConfiguration.defaultSubspace
339+
subspace: defaultSubspace
335340
});
336341
});
337342
}
@@ -340,14 +345,12 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
340345
await measureAsyncFn(`${PERF_PREFIX}:checkInstallFlag`, async () => {
341346
// TODO: Replace with last-install.flag when "rush link" and "rush unlink" are removed
342347
const lastLinkFlag: FlagFile = new FlagFile(
343-
this.rushConfiguration.defaultSubspace.getSubspaceTempFolderPath(),
348+
defaultSubspace.getSubspaceTempFolderPath(),
344349
RushConstants.lastLinkFlagFilename,
345350
{}
346351
);
347352
// Only check for a valid link flag when subspaces is not enabled
348-
if (!(await lastLinkFlag.isValidAsync()) && !this.rushConfiguration.subspacesFeatureEnabled) {
349-
const useWorkspaces: boolean =
350-
this.rushConfiguration.pnpmOptions && this.rushConfiguration.pnpmOptions.useWorkspaces;
353+
if (!(await lastLinkFlag.isValidAsync()) && !subspacesFeatureEnabled) {
351354
if (useWorkspaces) {
352355
throw new Error('Link flag invalid.\nDid you run "rush install" or "rush update"?');
353356
} else {
@@ -473,18 +476,27 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
473476
).IPCOperationRunnerPlugin().apply(this.hooks);
474477
}
475478

479+
const {
480+
experimentsConfiguration: {
481+
configuration: {
482+
buildCacheWithAllowWarningsInSuccessfulBuild = false,
483+
buildSkipWithAllowWarningsInSuccessfulBuild,
484+
omitAppleDoubleFilesFromBuildCache: excludeAppleDoubleFiles = false,
485+
useDirectFileTransfersForBuildCache = false,
486+
usePnpmSyncForInjectedDependencies
487+
}
488+
},
489+
isPnpm
490+
} = this.rushConfiguration;
476491
if (buildCacheConfiguration?.buildCacheEnabled) {
477492
terminal.writeVerboseLine(`Incremental strategy: cache restoration`);
478493
new CacheableOperationPlugin({
479-
allowWarningsInSuccessfulBuild:
480-
!!this.rushConfiguration.experimentsConfiguration.configuration
481-
.buildCacheWithAllowWarningsInSuccessfulBuild,
494+
allowWarningsInSuccessfulBuild: buildCacheWithAllowWarningsInSuccessfulBuild,
482495
buildCacheConfiguration,
483496
cobuildConfiguration,
484497
terminal,
485-
excludeAppleDoubleFiles:
486-
!!this.rushConfiguration.experimentsConfiguration.configuration
487-
.omitAppleDoubleFilesFromBuildCache
498+
excludeAppleDoubleFiles,
499+
useDirectFileTransfersForBuildCache
488500
}).apply(this.hooks);
489501

490502
if (this._debugBuildCacheIdsParameter.value) {
@@ -494,9 +506,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
494506
terminal.writeVerboseLine(`Incremental strategy: output preservation`);
495507
// Explicitly disabling the build cache also disables legacy skip detection.
496508
new LegacySkipPlugin({
497-
allowWarningsInSuccessfulBuild:
498-
this.rushConfiguration.experimentsConfiguration.configuration
499-
.buildSkipWithAllowWarningsInSuccessfulBuild,
509+
allowWarningsInSuccessfulBuild: buildSkipWithAllowWarningsInSuccessfulBuild,
500510
terminal,
501511
changedProjectsOnly,
502512
isIncrementalBuildAllowed: this._isIncrementalBuildAllowed
@@ -511,12 +521,12 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
511521
if (!buildCacheConfiguration?.buildCacheEnabled) {
512522
throw new Error('You must have build cache enabled to use this option.');
513523
}
524+
514525
const { BuildPlanPlugin } = await import('../../logic/operations/BuildPlanPlugin');
515526
new BuildPlanPlugin(terminal).apply(this.hooks);
516527
}
517528

518-
const { configuration: experiments } = this.rushConfiguration.experimentsConfiguration;
519-
if (this.rushConfiguration?.isPnpm && experiments?.usePnpmSyncForInjectedDependencies) {
529+
if (isPnpm && usePnpmSyncForInjectedDependencies) {
520530
const { PnpmSyncCopyOperationPlugin } = await import(
521531
'../../logic/operations/PnpmSyncCopyOperationPlugin'
522532
);

libraries/rush-lib/src/logic/PublishUtilities.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,11 @@ export class PublishUtilities {
390390
currentWorkingDirectory: path.dirname(filename)
391391
}
392392
);
393-
const { stdout: fileLog, exitCode, signal } = await Executable.waitForExitAsync(gitProcess, {
393+
const {
394+
stdout: fileLog,
395+
exitCode,
396+
signal
397+
} = await Executable.waitForExitAsync(gitProcess, {
394398
encoding: 'utf8'
395399
});
396400
if (exitCode !== 0 || signal) {

libraries/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
import * as path from 'node:path';
5-
64
import { FileSystem } from '@rushstack/node-core-library';
75
import type { ITerminal } from '@rushstack/terminal';
86

@@ -32,19 +30,17 @@ const DEFAULT_BUILD_CACHE_FOLDER_NAME: string = 'build-cache';
3230
* @beta
3331
*/
3432
export class FileSystemBuildCacheProvider {
35-
private readonly _cacheFolderPath: string;
36-
37-
public constructor(options: IFileSystemBuildCacheProviderOptions) {
38-
this._cacheFolderPath =
39-
options.rushUserConfiguration.buildCacheFolder ||
40-
path.join(options.rushConfiguration.commonTempFolder, DEFAULT_BUILD_CACHE_FOLDER_NAME);
41-
}
42-
4333
/**
4434
* Returns the absolute disk path for the specified cache id.
4535
*/
46-
public getCacheEntryPath(cacheId: string): string {
47-
return path.join(this._cacheFolderPath, cacheId);
36+
public readonly getCacheEntryPath: (cacheId: string) => string;
37+
38+
public constructor(options: IFileSystemBuildCacheProviderOptions) {
39+
const {
40+
rushConfiguration: { commonTempFolder },
41+
rushUserConfiguration: { buildCacheFolder = `${commonTempFolder}/${DEFAULT_BUILD_CACHE_FOLDER_NAME}` }
42+
} = options;
43+
this.getCacheEntryPath = (cacheId: string) => `${buildCacheFolder}/${cacheId}`;
4844
}
4945

5046
/**
@@ -55,7 +51,8 @@ export class FileSystemBuildCacheProvider {
5551
cacheId: string
5652
): Promise<string | undefined> {
5753
const cacheEntryFilePath: string = this.getCacheEntryPath(cacheId);
58-
if (await FileSystem.existsAsync(cacheEntryFilePath)) {
54+
const cacheEntryExists: boolean = await FileSystem.existsAsync(cacheEntryFilePath);
55+
if (cacheEntryExists) {
5956
return cacheEntryFilePath;
6057
} else {
6158
return undefined;

libraries/rush-lib/src/logic/buildCache/ICloudBuildCacheProvider.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,36 @@ export interface ICloudBuildCacheProvider {
1111

1212
tryGetCacheEntryBufferByIdAsync(terminal: ITerminal, cacheId: string): Promise<Buffer | undefined>;
1313
trySetCacheEntryBufferAsync(terminal: ITerminal, cacheId: string, entryBuffer: Buffer): Promise<boolean>;
14+
15+
/**
16+
* If implemented, the build cache will prefer to use this method over
17+
* {@link ICloudBuildCacheProvider.tryGetCacheEntryBufferByIdAsync} to avoid loading the entire
18+
* cache entry into memory, if possible. The implementation should download the cache entry and write it
19+
* to the specified local file path.
20+
*
21+
* @returns `true` if the cache entry was found and written to the file; otherwise `false`.
22+
* Implementations typically log transfer failures and return `false`, but may still throw for
23+
* unexpected errors.
24+
*/
25+
tryDownloadCacheEntryToFileAsync?(
26+
terminal: ITerminal,
27+
cacheId: string,
28+
localFilePath: string
29+
): Promise<boolean>;
30+
/**
31+
* If implemented, the build cache will prefer to use this method over
32+
* {@link ICloudBuildCacheProvider.trySetCacheEntryBufferAsync} to avoid loading the entire
33+
* cache entry into memory, if possible. The implementation should read the cache entry from
34+
* the specified local file path and upload it.
35+
*
36+
* @returns `true` if the cache entry was written to the cache, otherwise `false`.
37+
*/
38+
tryUploadCacheEntryFromFileAsync?(
39+
terminal: ITerminal,
40+
cacheId: string,
41+
localFilePath: string
42+
): Promise<boolean>;
43+
1444
updateCachedCredentialAsync(terminal: ITerminal, credential: string): Promise<void>;
1545
updateCachedCredentialInteractiveAsync(terminal: ITerminal): Promise<void>;
1646
deleteCachedCredentialsAsync(terminal: ITerminal): Promise<void>;

0 commit comments

Comments
 (0)