diff --git a/common/changes/@microsoft/rush/feat-custom-endpoint-azure-blob-storage_2026-02-23-03-40.json b/common/changes/@microsoft/rush/feat-custom-endpoint-azure-blob-storage_2026-02-23-03-40.json new file mode 100644 index 00000000000..27740282c27 --- /dev/null +++ b/common/changes/@microsoft/rush/feat-custom-endpoint-azure-blob-storage_2026-02-23-03-40.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add custom endpoint support via a `storageEndpoint` configuration option for the Azure Storage build cache plugin.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md b/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md index 26f39c4993a..f093e5a8a62 100644 --- a/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md +++ b/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md @@ -98,6 +98,8 @@ export interface IAzureStorageAuthenticationOptions extends IAzureAuthentication storageAccountName: string; // (undocumented) storageContainerName: string; + // (undocumented) + storageEndpoint?: string; } // @public (undocumented) diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureStorageAuthentication.ts b/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureStorageAuthentication.ts index 982eb9f51fd..fdd1ecf80a4 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureStorageAuthentication.ts +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureStorageAuthentication.ts @@ -24,6 +24,7 @@ import { export interface IAzureStorageAuthenticationOptions extends IAzureAuthenticationBaseOptions { storageContainerName: string; storageAccountName: string; + storageEndpoint?: string; isCacheWriteAllowed: boolean; } @@ -46,7 +47,11 @@ export class AzureStorageAuthentication extends AzureAuthenticationBase { this._storageAccountName = options.storageAccountName; this._storageContainerName = options.storageContainerName; this._isCacheWriteAllowedByConfiguration = options.isCacheWriteAllowed; - this._storageAccountUrl = `https://${this._storageAccountName}.blob.core.windows.net/`; + this._storageAccountUrl = options.storageEndpoint + ? options.storageEndpoint.endsWith('/') + ? options.storageEndpoint + : options.storageEndpoint + '/' + : `https://${this._storageAccountName}.blob.core.windows.net/`; } protected _getCacheIdParts(): string[] { diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/RushAzureStorageBuildCachePlugin.ts b/rush-plugins/rush-azure-storage-build-cache-plugin/src/RushAzureStorageBuildCachePlugin.ts index 4f3f440c5f7..4955f96f27b 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/RushAzureStorageBuildCachePlugin.ts +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/RushAzureStorageBuildCachePlugin.ts @@ -37,6 +37,13 @@ interface IAzureBlobStorageConfigurationJson { */ readonly loginFlowFailover?: LoginFlowFailoverMap; + /** + * An optional custom endpoint URL for the Azure Blob Storage account. + * Use this to connect to Azurite, private endpoints, or storage emulators. + * Overrides the default endpoint derived from storageAccountName. + */ + readonly storageEndpoint?: string; + /** * An optional prefix for cache item blob names. */ @@ -70,6 +77,7 @@ export class RushAzureStorageBuildCachePlugin implements IRushPlugin { return new AzureStorageBuildCacheProvider({ storageAccountName: azureBlobStorageConfiguration.storageAccountName, storageContainerName: azureBlobStorageConfiguration.storageContainerName, + storageEndpoint: azureBlobStorageConfiguration.storageEndpoint, azureEnvironment: azureBlobStorageConfiguration.azureEnvironment, blobPrefix: azureBlobStorageConfiguration.blobPrefix, loginFlow: azureBlobStorageConfiguration.loginFlow, diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/schemas/azure-blob-storage-config.schema.json b/rush-plugins/rush-azure-storage-build-cache-plugin/src/schemas/azure-blob-storage-config.schema.json index 3a5173a26fd..2ffcd58e4ba 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/schemas/azure-blob-storage-config.schema.json +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/schemas/azure-blob-storage-config.schema.json @@ -95,6 +95,12 @@ "description": "An optional prefix for cache item blob names." }, + "storageEndpoint": { + "type": "string", + "description": "An optional custom endpoint URL for the Azure Blob Storage account. Use this to connect to Azurite, private endpoints, or storage emulators. When specified, this overrides the default endpoint derived from storageAccountName. Example: \"http://127.0.0.1:10000/devstoreaccount1\" or \"https://my-proxy.example.com/devstoreaccount1\"", + "format": "uri" + }, + "isCacheWriteAllowed": { "type": "boolean", "description": "If set to true, allow writing to the cache. Defaults to false." diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts b/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts index 23bb0ead55b..25cb7d50bc1 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts @@ -31,6 +31,40 @@ describe(AzureStorageBuildCacheProvider.name, () => { ).toThrowErrorMatchingSnapshot(); }); + describe('storageEndpoint', () => { + it('uses the default endpoint when storageEndpoint is not provided', () => { + const subject: AzureStorageBuildCacheProvider = new AzureStorageBuildCacheProvider({ + storageAccountName: 'storage-account', + storageContainerName: 'container-name', + isCacheWriteAllowed: false + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((subject as any)._storageAccountUrl).toBe('https://storage-account.blob.core.windows.net/'); + }); + + it('uses the custom endpoint when storageEndpoint is provided', () => { + const subject: AzureStorageBuildCacheProvider = new AzureStorageBuildCacheProvider({ + storageAccountName: 'devstoreaccount1', + storageContainerName: 'container-name', + storageEndpoint: 'http://127.0.0.1:10000/devstoreaccount1', + isCacheWriteAllowed: false + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((subject as any)._storageAccountUrl).toBe('http://127.0.0.1:10000/devstoreaccount1/'); + }); + + it('preserves a trailing slash on the custom endpoint', () => { + const subject: AzureStorageBuildCacheProvider = new AzureStorageBuildCacheProvider({ + storageAccountName: 'devstoreaccount1', + storageContainerName: 'container-name', + storageEndpoint: 'https://my-proxy.example.com/devstoreaccount1/', + isCacheWriteAllowed: false + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((subject as any)._storageAccountUrl).toBe('https://my-proxy.example.com/devstoreaccount1/'); + }); + }); + describe('isCacheWriteAllowed', () => { function prepareSubject( optionValue: boolean,