@rushstack/rush-amazon-s3-build-cache-plugin 5.168.0 → 5.169.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/{lib → lib-dts}/tsdoc-metadata.json +1 -1
  2. package/lib-esm/AmazonS3BuildCacheProvider.js +131 -0
  3. package/lib-esm/AmazonS3BuildCacheProvider.js.map +1 -0
  4. package/lib-esm/AmazonS3Client.js +364 -0
  5. package/lib-esm/AmazonS3Client.js.map +1 -0
  6. package/lib-esm/AmazonS3Credentials.js +50 -0
  7. package/lib-esm/AmazonS3Credentials.js.map +1 -0
  8. package/lib-esm/RushAmazonS3BuildCachePlugin.js +53 -0
  9. package/lib-esm/RushAmazonS3BuildCachePlugin.js.map +1 -0
  10. package/lib-esm/index.js +7 -0
  11. package/lib-esm/index.js.map +1 -0
  12. package/lib-esm/schemas/amazon-s3-config.schema.json +51 -0
  13. package/package.json +33 -9
  14. package/rush-plugin-manifest.json +2 -2
  15. /package/{lib → lib-commonjs}/AmazonS3BuildCacheProvider.js +0 -0
  16. /package/{lib → lib-commonjs}/AmazonS3BuildCacheProvider.js.map +0 -0
  17. /package/{lib → lib-commonjs}/AmazonS3Client.js +0 -0
  18. /package/{lib → lib-commonjs}/AmazonS3Client.js.map +0 -0
  19. /package/{lib → lib-commonjs}/AmazonS3Credentials.js +0 -0
  20. /package/{lib → lib-commonjs}/AmazonS3Credentials.js.map +0 -0
  21. /package/{lib → lib-commonjs}/RushAmazonS3BuildCachePlugin.js +0 -0
  22. /package/{lib → lib-commonjs}/RushAmazonS3BuildCachePlugin.js.map +0 -0
  23. /package/{lib → lib-commonjs}/index.js +0 -0
  24. /package/{lib → lib-commonjs}/index.js.map +0 -0
  25. /package/{lib → lib-commonjs}/schemas/amazon-s3-config.schema.json +0 -0
  26. /package/{lib → lib-dts}/AmazonS3BuildCacheProvider.d.ts +0 -0
  27. /package/{lib → lib-dts}/AmazonS3BuildCacheProvider.d.ts.map +0 -0
  28. /package/{lib → lib-dts}/AmazonS3Client.d.ts +0 -0
  29. /package/{lib → lib-dts}/AmazonS3Client.d.ts.map +0 -0
  30. /package/{lib → lib-dts}/AmazonS3Credentials.d.ts +0 -0
  31. /package/{lib → lib-dts}/AmazonS3Credentials.d.ts.map +0 -0
  32. /package/{lib → lib-dts}/RushAmazonS3BuildCachePlugin.d.ts +0 -0
  33. /package/{lib → lib-dts}/RushAmazonS3BuildCachePlugin.d.ts.map +0 -0
  34. /package/{lib → lib-dts}/index.d.ts +0 -0
  35. /package/{lib → lib-dts}/index.d.ts.map +0 -0
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.56.3"
8
+ "packageVersion": "7.57.0"
9
9
  }
10
10
  ]
11
11
  }
@@ -0,0 +1,131 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { CredentialCache } from '@rushstack/credential-cache';
4
+ import { RushConstants, EnvironmentVariableNames, EnvironmentConfiguration } from '@rushstack/rush-sdk';
5
+ import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient';
6
+ import { AmazonS3Client } from './AmazonS3Client';
7
+ import { fromAmazonEnv, fromRushEnv } from './AmazonS3Credentials';
8
+ const DEFAULT_S3_REGION = 'us-east-1';
9
+ export class AmazonS3BuildCacheProvider {
10
+ get isCacheWriteAllowed() {
11
+ var _a;
12
+ return (_a = EnvironmentConfiguration.buildCacheWriteAllowed) !== null && _a !== void 0 ? _a : this._isCacheWriteAllowedByConfiguration;
13
+ }
14
+ constructor(options, rushSession) {
15
+ this._rushSession = rushSession;
16
+ this._options = options;
17
+ this._s3Prefix = options.s3Prefix;
18
+ this._isCacheWriteAllowedByConfiguration = options.isCacheWriteAllowed;
19
+ }
20
+ get _s3Endpoint() {
21
+ const options = this._options;
22
+ if ('s3Bucket' in options) {
23
+ // options: IAmazonS3BuildCacheProviderOptionsSimple
24
+ const bucket = options.s3Bucket;
25
+ if (options.s3Region === DEFAULT_S3_REGION) {
26
+ return `https://${bucket}.s3.amazonaws.com`;
27
+ }
28
+ else {
29
+ return `https://${bucket}.s3-${options.s3Region}.amazonaws.com`;
30
+ }
31
+ }
32
+ // options: IAmazonS3BuildCacheProviderOptionsAdvanced
33
+ return options.s3Endpoint;
34
+ }
35
+ get _credentialCacheId() {
36
+ if (!this.__credentialCacheId) {
37
+ const cacheIdParts = ['aws-s3', this._options.s3Region, this._s3Endpoint];
38
+ if (this._isCacheWriteAllowedByConfiguration) {
39
+ cacheIdParts.push('cacheWriteAllowed');
40
+ }
41
+ this.__credentialCacheId = cacheIdParts.join('|');
42
+ }
43
+ return this.__credentialCacheId;
44
+ }
45
+ async _getS3ClientAsync(terminal) {
46
+ var _a, _b;
47
+ if (!this.__s3Client) {
48
+ let credentials = (_a = fromRushEnv()) !== null && _a !== void 0 ? _a : fromAmazonEnv();
49
+ if (!credentials) {
50
+ terminal.writeDebugLine('No credentials found in env. Trying cloud credentials.');
51
+ let cacheEntry;
52
+ await CredentialCache.usingAsync({
53
+ supportEditing: false
54
+ }, (credentialsCache) => {
55
+ cacheEntry = credentialsCache.tryGetCacheEntry(this._credentialCacheId);
56
+ });
57
+ if (cacheEntry) {
58
+ const expirationTime = (_b = cacheEntry.expires) === null || _b === void 0 ? void 0 : _b.getTime();
59
+ if (expirationTime && expirationTime < Date.now()) {
60
+ throw new Error('Cached Amazon S3 credentials have expired. ' +
61
+ `Update the credentials by running "rush ${RushConstants.updateCloudCredentialsCommandName}".`);
62
+ }
63
+ else {
64
+ credentials = fromRushEnv(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.credential);
65
+ }
66
+ }
67
+ else if (this._isCacheWriteAllowedByConfiguration) {
68
+ throw new Error("An Amazon S3 credential hasn't been provided, or has expired. " +
69
+ `Update the credentials by running "rush ${RushConstants.updateCloudCredentialsCommandName}", ` +
70
+ `or provide an <AccessKeyId>:<SecretAccessKey> pair in the ` +
71
+ `${EnvironmentVariableNames.RUSH_BUILD_CACHE_CREDENTIAL} environment variable`);
72
+ }
73
+ }
74
+ this.__s3Client = new AmazonS3Client(credentials, {
75
+ ...this._options,
76
+ // advanced options
77
+ s3Endpoint: this._s3Endpoint
78
+ }, new WebClient(), terminal);
79
+ }
80
+ return this.__s3Client;
81
+ }
82
+ async tryGetCacheEntryBufferByIdAsync(terminal, cacheId) {
83
+ try {
84
+ const client = await this._getS3ClientAsync(terminal);
85
+ return await client.getObjectAsync(this._s3Prefix ? `${this._s3Prefix}/${cacheId}` : cacheId);
86
+ }
87
+ catch (e) {
88
+ terminal.writeWarningLine(`Error getting cache entry from S3: ${e}`);
89
+ return undefined;
90
+ }
91
+ }
92
+ async trySetCacheEntryBufferAsync(terminal, cacheId, objectBuffer) {
93
+ if (!this.isCacheWriteAllowed) {
94
+ terminal.writeErrorLine('Writing to S3 cache is not allowed in the current configuration.');
95
+ return false;
96
+ }
97
+ terminal.writeDebugLine('Uploading object with cacheId: ', cacheId);
98
+ try {
99
+ const client = await this._getS3ClientAsync(terminal);
100
+ await client.uploadObjectAsync(this._s3Prefix ? `${this._s3Prefix}/${cacheId}` : cacheId, objectBuffer);
101
+ return true;
102
+ }
103
+ catch (e) {
104
+ terminal.writeWarningLine(`Error uploading cache entry to S3: ${e}`);
105
+ return false;
106
+ }
107
+ }
108
+ async updateCachedCredentialAsync(terminal, credential) {
109
+ await CredentialCache.usingAsync({
110
+ supportEditing: true
111
+ }, async (credentialsCache) => {
112
+ credentialsCache.setCacheEntry(this._credentialCacheId, { credential });
113
+ await credentialsCache.saveIfModifiedAsync();
114
+ });
115
+ }
116
+ async updateCachedCredentialInteractiveAsync(terminal) {
117
+ throw new Error('The interactive cloud credentials flow is not supported for Amazon S3.\n' +
118
+ 'Provide your credentials to rush using the --credential flag instead. Credentials must be ' +
119
+ 'in the form of <ACCESS KEY ID>:<SECRET ACCESS KEY> or ' +
120
+ '<ACCESS KEY ID>:<SECRET ACCESS KEY>:<SESSION TOKEN>.');
121
+ }
122
+ async deleteCachedCredentialsAsync(terminal) {
123
+ await CredentialCache.usingAsync({
124
+ supportEditing: true
125
+ }, async (credentialsCache) => {
126
+ credentialsCache.deleteCacheEntry(this._credentialCacheId);
127
+ await credentialsCache.saveIfModifiedAsync();
128
+ });
129
+ }
130
+ }
131
+ //# sourceMappingURL=AmazonS3BuildCacheProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AmazonS3BuildCacheProvider.js","sourceRoot":"","sources":["../src/AmazonS3BuildCacheProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { type ICredentialCacheEntry, CredentialCache } from '@rushstack/credential-cache';\nimport type { ITerminal } from '@rushstack/terminal';\nimport {\n type ICloudBuildCacheProvider,\n type RushSession,\n RushConstants,\n EnvironmentVariableNames,\n EnvironmentConfiguration\n} from '@rushstack/rush-sdk';\nimport { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient';\n\nimport { AmazonS3Client } from './AmazonS3Client';\nimport { type IAmazonS3Credentials, fromAmazonEnv, fromRushEnv } from './AmazonS3Credentials';\n\n/**\n * @public\n */\nexport interface IAmazonS3BuildCacheProviderOptionsBase {\n s3Region: string;\n s3Prefix: string | undefined;\n isCacheWriteAllowed: boolean;\n}\n\n/**\n * Advanced options where user has the specify the full http endpoint\n * @public\n */\nexport interface IAmazonS3BuildCacheProviderOptionsAdvanced extends IAmazonS3BuildCacheProviderOptionsBase {\n s3Endpoint: string;\n}\n/**\n * Simple options where user only provides the bucket and the endpoint is automatically built\n * @public\n */\nexport interface IAmazonS3BuildCacheProviderOptionsSimple extends IAmazonS3BuildCacheProviderOptionsBase {\n s3Bucket: string;\n}\n\nconst DEFAULT_S3_REGION: 'us-east-1' = 'us-east-1';\nexport class AmazonS3BuildCacheProvider implements ICloudBuildCacheProvider {\n private readonly _options:\n | IAmazonS3BuildCacheProviderOptionsSimple\n | IAmazonS3BuildCacheProviderOptionsAdvanced;\n private readonly _s3Prefix: string | undefined;\n private readonly _isCacheWriteAllowedByConfiguration: boolean;\n private __credentialCacheId: string | undefined;\n private _rushSession: RushSession;\n\n public get isCacheWriteAllowed(): boolean {\n return EnvironmentConfiguration.buildCacheWriteAllowed ?? this._isCacheWriteAllowedByConfiguration;\n }\n\n private __s3Client: AmazonS3Client | undefined;\n\n public constructor(\n options: IAmazonS3BuildCacheProviderOptionsSimple | IAmazonS3BuildCacheProviderOptionsAdvanced,\n rushSession: RushSession\n ) {\n this._rushSession = rushSession;\n this._options = options;\n this._s3Prefix = options.s3Prefix;\n this._isCacheWriteAllowedByConfiguration = options.isCacheWriteAllowed;\n }\n\n private get _s3Endpoint(): string {\n const options: IAmazonS3BuildCacheProviderOptionsSimple | IAmazonS3BuildCacheProviderOptionsAdvanced =\n this._options;\n if ('s3Bucket' in options) {\n // options: IAmazonS3BuildCacheProviderOptionsSimple\n const bucket: string = options.s3Bucket;\n if (options.s3Region === DEFAULT_S3_REGION) {\n return `https://${bucket}.s3.amazonaws.com`;\n } else {\n return `https://${bucket}.s3-${options.s3Region}.amazonaws.com`;\n }\n }\n // options: IAmazonS3BuildCacheProviderOptionsAdvanced\n return options.s3Endpoint;\n }\n\n private get _credentialCacheId(): string {\n if (!this.__credentialCacheId) {\n const cacheIdParts: string[] = ['aws-s3', this._options.s3Region, this._s3Endpoint];\n\n if (this._isCacheWriteAllowedByConfiguration) {\n cacheIdParts.push('cacheWriteAllowed');\n }\n\n this.__credentialCacheId = cacheIdParts.join('|');\n }\n\n return this.__credentialCacheId;\n }\n\n private async _getS3ClientAsync(terminal: ITerminal): Promise<AmazonS3Client> {\n if (!this.__s3Client) {\n let credentials: IAmazonS3Credentials | undefined = fromRushEnv() ?? fromAmazonEnv();\n\n if (!credentials) {\n terminal.writeDebugLine('No credentials found in env. Trying cloud credentials.');\n\n let cacheEntry: ICredentialCacheEntry | undefined;\n await CredentialCache.usingAsync(\n {\n supportEditing: false\n },\n (credentialsCache: CredentialCache) => {\n cacheEntry = credentialsCache.tryGetCacheEntry(this._credentialCacheId);\n }\n );\n\n if (cacheEntry) {\n const expirationTime: number | undefined = cacheEntry.expires?.getTime();\n if (expirationTime && expirationTime < Date.now()) {\n throw new Error(\n 'Cached Amazon S3 credentials have expired. ' +\n `Update the credentials by running \"rush ${RushConstants.updateCloudCredentialsCommandName}\".`\n );\n } else {\n credentials = fromRushEnv(cacheEntry?.credential);\n }\n } else if (this._isCacheWriteAllowedByConfiguration) {\n throw new Error(\n \"An Amazon S3 credential hasn't been provided, or has expired. \" +\n `Update the credentials by running \"rush ${RushConstants.updateCloudCredentialsCommandName}\", ` +\n `or provide an <AccessKeyId>:<SecretAccessKey> pair in the ` +\n `${EnvironmentVariableNames.RUSH_BUILD_CACHE_CREDENTIAL} environment variable`\n );\n }\n }\n\n this.__s3Client = new AmazonS3Client(\n credentials,\n {\n ...this._options,\n // advanced options\n s3Endpoint: this._s3Endpoint\n },\n new WebClient(),\n terminal\n );\n }\n\n return this.__s3Client;\n }\n\n public async tryGetCacheEntryBufferByIdAsync(\n terminal: ITerminal,\n cacheId: string\n ): Promise<Buffer | undefined> {\n try {\n const client: AmazonS3Client = await this._getS3ClientAsync(terminal);\n return await client.getObjectAsync(this._s3Prefix ? `${this._s3Prefix}/${cacheId}` : cacheId);\n } catch (e) {\n terminal.writeWarningLine(`Error getting cache entry from S3: ${e}`);\n return undefined;\n }\n }\n\n public async trySetCacheEntryBufferAsync(\n terminal: ITerminal,\n cacheId: string,\n objectBuffer: Buffer\n ): Promise<boolean> {\n if (!this.isCacheWriteAllowed) {\n terminal.writeErrorLine('Writing to S3 cache is not allowed in the current configuration.');\n return false;\n }\n\n terminal.writeDebugLine('Uploading object with cacheId: ', cacheId);\n\n try {\n const client: AmazonS3Client = await this._getS3ClientAsync(terminal);\n await client.uploadObjectAsync(this._s3Prefix ? `${this._s3Prefix}/${cacheId}` : cacheId, objectBuffer);\n return true;\n } catch (e) {\n terminal.writeWarningLine(`Error uploading cache entry to S3: ${e}`);\n return false;\n }\n }\n\n public async updateCachedCredentialAsync(terminal: ITerminal, credential: string): Promise<void> {\n await CredentialCache.usingAsync(\n {\n supportEditing: true\n },\n async (credentialsCache: CredentialCache) => {\n credentialsCache.setCacheEntry(this._credentialCacheId, { credential });\n await credentialsCache.saveIfModifiedAsync();\n }\n );\n }\n\n public async updateCachedCredentialInteractiveAsync(terminal: ITerminal): Promise<void> {\n throw new Error(\n 'The interactive cloud credentials flow is not supported for Amazon S3.\\n' +\n 'Provide your credentials to rush using the --credential flag instead. Credentials must be ' +\n 'in the form of <ACCESS KEY ID>:<SECRET ACCESS KEY> or ' +\n '<ACCESS KEY ID>:<SECRET ACCESS KEY>:<SESSION TOKEN>.'\n );\n }\n\n public async deleteCachedCredentialsAsync(terminal: ITerminal): Promise<void> {\n await CredentialCache.usingAsync(\n {\n supportEditing: true\n },\n async (credentialsCache: CredentialCache) => {\n credentialsCache.deleteCacheEntry(this._credentialCacheId);\n await credentialsCache.saveIfModifiedAsync();\n }\n );\n }\n}\n"]}
@@ -0,0 +1,364 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import * as crypto from 'node:crypto';
4
+ import { Async } from '@rushstack/node-core-library';
5
+ import { Colorize } from '@rushstack/terminal';
6
+ import { AUTHORIZATION_HEADER_NAME } from '@rushstack/rush-sdk/lib/utilities/WebClient';
7
+ import { fromRushEnv } from './AmazonS3Credentials';
8
+ const CONTENT_HASH_HEADER_NAME = 'x-amz-content-sha256';
9
+ const DATE_HEADER_NAME = 'x-amz-date';
10
+ const HOST_HEADER_NAME = 'host';
11
+ const SECURITY_TOKEN_HEADER_NAME = 'x-amz-security-token';
12
+ const protocolRegex = /^https?:\/\//;
13
+ const portRegex = /:(\d{1,5})$/;
14
+ // Similar to https://docs.microsoft.com/en-us/javascript/api/@azure/storage-blob/storageretrypolicytype?view=azure-node-latest
15
+ var StorageRetryPolicyType;
16
+ (function (StorageRetryPolicyType) {
17
+ StorageRetryPolicyType[StorageRetryPolicyType["EXPONENTIAL"] = 0] = "EXPONENTIAL";
18
+ StorageRetryPolicyType[StorageRetryPolicyType["FIXED"] = 1] = "FIXED";
19
+ })(StorageRetryPolicyType || (StorageRetryPolicyType = {}));
20
+ const storageRetryOptions = {
21
+ maxRetryDelayInMs: 120 * 1000,
22
+ maxTries: 4,
23
+ retryDelayInMs: 4 * 1000,
24
+ retryPolicyType: StorageRetryPolicyType.EXPONENTIAL
25
+ };
26
+ /**
27
+ * A helper for reading and updating objects on Amazon S3
28
+ *
29
+ * @public
30
+ */
31
+ export class AmazonS3Client {
32
+ constructor(credentials, options, webClient, terminal) {
33
+ this._credentials = credentials;
34
+ this._terminal = terminal;
35
+ this._validateEndpoint(options.s3Endpoint);
36
+ this._s3Endpoint = options.s3Endpoint;
37
+ this._s3Region = options.s3Region;
38
+ this._webClient = webClient;
39
+ }
40
+ // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#create-signature-presign-entire-payload
41
+ // We want to keep all slashes non encoded
42
+ static UriEncode(input) {
43
+ let output = '';
44
+ for (let i = 0; i < input.length; i += 1) {
45
+ const ch = input[i];
46
+ if (ch.match(/[A-Za-z0-9~._-]|\//)) {
47
+ output += ch;
48
+ }
49
+ else {
50
+ if (ch === ' ') {
51
+ output += '%20';
52
+ }
53
+ else {
54
+ output += `%${ch.charCodeAt(0).toString(16).toUpperCase()}`;
55
+ }
56
+ }
57
+ }
58
+ return output;
59
+ }
60
+ static tryDeserializeCredentials(credentialString) {
61
+ return fromRushEnv(credentialString);
62
+ }
63
+ async getObjectAsync(objectName) {
64
+ this._writeDebugLine('Reading object from S3');
65
+ return await this._sendCacheRequestWithRetriesAsync(async () => {
66
+ const response = await this._makeRequestAsync('GET', objectName);
67
+ if (response.ok) {
68
+ return {
69
+ hasNetworkError: false,
70
+ response: await response.getBufferAsync()
71
+ };
72
+ }
73
+ else if (response.status === 404) {
74
+ return {
75
+ hasNetworkError: false,
76
+ response: undefined
77
+ };
78
+ }
79
+ else if ((response.status === 400 || response.status === 401 || response.status === 403) &&
80
+ !this._credentials) {
81
+ // unauthorized due to not providing credentials,
82
+ // silence error for better DX when e.g. running locally without credentials
83
+ this._writeWarningLine(`No credentials found and received a ${response.status}`, ' response code from the cloud storage.', ' Maybe run rush update-cloud-credentials', ' or set the RUSH_BUILD_CACHE_CREDENTIAL env');
84
+ return {
85
+ hasNetworkError: false,
86
+ response: undefined
87
+ };
88
+ }
89
+ else if (response.status === 400 || response.status === 401 || response.status === 403) {
90
+ throw await this._getS3ErrorAsync(response);
91
+ }
92
+ else {
93
+ const error = await this._getS3ErrorAsync(response);
94
+ return {
95
+ hasNetworkError: true,
96
+ error
97
+ };
98
+ }
99
+ });
100
+ }
101
+ async uploadObjectAsync(objectName, objectBuffer) {
102
+ if (!this._credentials) {
103
+ throw new Error('Credentials are required to upload objects to S3.');
104
+ }
105
+ await this._sendCacheRequestWithRetriesAsync(async () => {
106
+ const response = await this._makeRequestAsync('PUT', objectName, objectBuffer);
107
+ if (!response.ok) {
108
+ return {
109
+ hasNetworkError: true,
110
+ error: await this._getS3ErrorAsync(response)
111
+ };
112
+ }
113
+ return {
114
+ hasNetworkError: false,
115
+ response: undefined
116
+ };
117
+ });
118
+ }
119
+ _writeDebugLine(...messageParts) {
120
+ // if the terminal has been closed then don't bother sending a debug message
121
+ try {
122
+ this._terminal.writeDebugLine(...messageParts);
123
+ }
124
+ catch (err) {
125
+ // ignore error
126
+ }
127
+ }
128
+ _writeWarningLine(...messageParts) {
129
+ // if the terminal has been closed then don't bother sending a warning message
130
+ try {
131
+ this._terminal.writeWarningLine(...messageParts);
132
+ }
133
+ catch (err) {
134
+ // ignore error
135
+ }
136
+ }
137
+ async _makeRequestAsync(verb, objectName, body) {
138
+ const isoDateString = this._getIsoDateString();
139
+ const bodyHash = this._getSha256(body);
140
+ const headers = {};
141
+ headers[DATE_HEADER_NAME] = isoDateString.dateTime;
142
+ headers[CONTENT_HASH_HEADER_NAME] = bodyHash;
143
+ // the host can be e.g. https://s3.aws.com or http://localhost:9000
144
+ const host = this._s3Endpoint.replace(protocolRegex, '');
145
+ const canonicalUri = AmazonS3Client.UriEncode(`/${objectName}`);
146
+ this._writeDebugLine(Colorize.bold('Canonical URI: '), canonicalUri);
147
+ if (this._credentials) {
148
+ // Compute the authorization header. See https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
149
+ const canonicalHeaders = [
150
+ `${HOST_HEADER_NAME}:${host}`,
151
+ `${CONTENT_HASH_HEADER_NAME}:${bodyHash}`,
152
+ `${DATE_HEADER_NAME}:${isoDateString.dateTime}`
153
+ ];
154
+ // Handle signing with temporary credentials (via sts:assume-role)
155
+ if (this._credentials.sessionToken) {
156
+ canonicalHeaders.push(`${SECURITY_TOKEN_HEADER_NAME}:${this._credentials.sessionToken}`);
157
+ }
158
+ // the canonical headers must be sorted by header name
159
+ canonicalHeaders.sort((aHeader, bHeader) => {
160
+ const aHeaderName = aHeader.split(':')[0];
161
+ const bHeaderName = bHeader.split(':')[0];
162
+ if (aHeaderName < bHeaderName) {
163
+ return -1;
164
+ }
165
+ if (aHeaderName > bHeaderName) {
166
+ return 1;
167
+ }
168
+ return 0;
169
+ });
170
+ // the singed header names are derived from the canonicalHeaders
171
+ const signedHeaderNamesString = canonicalHeaders
172
+ .map((header) => {
173
+ const headerName = header.split(':')[0];
174
+ return headerName;
175
+ })
176
+ .join(';');
177
+ // The canonical request looks like this:
178
+ // GET
179
+ // /test.txt
180
+ //
181
+ // host:examplebucket.s3.amazonaws.com
182
+ // range:bytes=0-9
183
+ // x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
184
+ // x-amz-date:20130524T000000Z
185
+ //
186
+ // host;range;x-amz-content-sha256;x-amz-date
187
+ // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
188
+ const canonicalRequest = [
189
+ verb,
190
+ canonicalUri,
191
+ '', // we don't use query strings for these requests
192
+ ...canonicalHeaders,
193
+ '',
194
+ signedHeaderNamesString,
195
+ bodyHash
196
+ ].join('\n');
197
+ const canonicalRequestHash = this._getSha256(canonicalRequest);
198
+ const scope = `${isoDateString.date}/${this._s3Region}/s3/aws4_request`;
199
+ // The string to sign looks like this:
200
+ // AWS4-HMAC-SHA256
201
+ // 20130524T423589Z
202
+ // 20130524/us-east-1/s3/aws4_request
203
+ // 7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972
204
+ const stringToSign = [
205
+ 'AWS4-HMAC-SHA256',
206
+ isoDateString.dateTime,
207
+ scope,
208
+ canonicalRequestHash
209
+ ].join('\n');
210
+ const dateKey = this._getSha256Hmac(`AWS4${this._credentials.secretAccessKey}`, isoDateString.date);
211
+ const dateRegionKey = this._getSha256Hmac(dateKey, this._s3Region);
212
+ const dateRegionServiceKey = this._getSha256Hmac(dateRegionKey, 's3');
213
+ const signingKey = this._getSha256Hmac(dateRegionServiceKey, 'aws4_request');
214
+ const signature = this._getSha256Hmac(signingKey, stringToSign, 'hex');
215
+ const authorizationHeader = `AWS4-HMAC-SHA256 Credential=${this._credentials.accessKeyId}/${scope},SignedHeaders=${signedHeaderNamesString},Signature=${signature}`;
216
+ headers[AUTHORIZATION_HEADER_NAME] = authorizationHeader;
217
+ if (this._credentials.sessionToken) {
218
+ // Handle signing with temporary credentials (via sts:assume-role)
219
+ headers['X-Amz-Security-Token'] = this._credentials.sessionToken;
220
+ }
221
+ }
222
+ const webFetchOptions = {
223
+ verb,
224
+ headers
225
+ };
226
+ if (verb === 'PUT') {
227
+ webFetchOptions.body = body;
228
+ }
229
+ const url = `${this._s3Endpoint}${canonicalUri}`;
230
+ this._writeDebugLine(Colorize.bold(Colorize.underline('Sending request to S3')));
231
+ this._writeDebugLine(Colorize.bold('HOST: '), url);
232
+ this._writeDebugLine(Colorize.bold('Headers: '));
233
+ for (const [name, value] of Object.entries(headers)) {
234
+ this._writeDebugLine(Colorize.cyan(`\t${name}: ${value}`));
235
+ }
236
+ const response = await this._webClient.fetchAsync(url, webFetchOptions);
237
+ return response;
238
+ }
239
+ _getSha256Hmac(key, data, encoding) {
240
+ const hash = crypto.createHmac('sha256', key);
241
+ hash.update(data);
242
+ if (encoding) {
243
+ return hash.digest(encoding);
244
+ }
245
+ else {
246
+ return hash.digest();
247
+ }
248
+ }
249
+ _getSha256(data) {
250
+ if (data) {
251
+ const hash = crypto.createHash('sha256');
252
+ hash.update(data);
253
+ return hash.digest('hex');
254
+ }
255
+ else {
256
+ // This is the null SHA256 hash
257
+ return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
258
+ }
259
+ }
260
+ _getIsoDateString(date = new Date()) {
261
+ let dateString = date.toISOString();
262
+ dateString = dateString.replace(/[-:]/g, ''); // Remove separator characters
263
+ dateString = dateString.substring(0, 15); // Drop milliseconds
264
+ // dateTime is an ISO8601 date. It looks like "20130524T423589"
265
+ // date is an ISO date. It looks like "20130524"
266
+ return {
267
+ dateTime: `${dateString}Z`,
268
+ date: dateString.substring(0, 8)
269
+ };
270
+ }
271
+ async _safeReadResponseTextAsync(response) {
272
+ try {
273
+ return await response.getTextAsync();
274
+ }
275
+ catch (err) {
276
+ // ignore the error
277
+ }
278
+ return undefined;
279
+ }
280
+ async _getS3ErrorAsync(response) {
281
+ const text = await this._safeReadResponseTextAsync(response);
282
+ return new Error(`Amazon S3 responded with status code ${response.status} (${response.statusText})${text ? `\n${text}` : ''}`);
283
+ }
284
+ /**
285
+ * Validates a S3 endpoint which is http(s):// + hostname + port. Hostname validated according to RFC 1123
286
+ * {@link https://docs.aws.amazon.com/general/latest/gr/s3.html}
287
+ */
288
+ _validateEndpoint(s3Endpoint) {
289
+ let host = s3Endpoint;
290
+ if (!s3Endpoint) {
291
+ throw new Error('A S3 endpoint must be provided');
292
+ }
293
+ if (!s3Endpoint.match(protocolRegex)) {
294
+ throw new Error('The S3 endpoint must start with https:// or http://');
295
+ }
296
+ host = host.replace(protocolRegex, '');
297
+ if (host.match(/\//)) {
298
+ throw new Error('The path should be omitted from the endpoint. Use s3Prefix to specify a path');
299
+ }
300
+ const portMatch = s3Endpoint.match(portRegex);
301
+ if (portMatch) {
302
+ const port = Number(portMatch[1]);
303
+ if (Number.isNaN(port) || port > 65535) {
304
+ throw new Error(`Port: ${port} is an invalid port number`);
305
+ }
306
+ host = host.replace(portRegex, '');
307
+ }
308
+ if (host.endsWith('.')) {
309
+ host = host.slice(0, host.length - 1);
310
+ }
311
+ if (host.length > 253) {
312
+ throw new Error('The S3 endpoint is too long. RFC 1123 specifies a hostname should be no longer than 253 characters.');
313
+ }
314
+ const subDomains = host.split('.');
315
+ const subDomainRegex = /^[a-zA-Z0-9-]+$/;
316
+ const isValid = subDomains.every((subDomain) => {
317
+ return (subDomainRegex.test(subDomain) &&
318
+ subDomain.length < 64 &&
319
+ !subDomain.startsWith('-') &&
320
+ !subDomain.endsWith('-'));
321
+ });
322
+ if (!isValid) {
323
+ throw new Error('Invalid S3 endpoint. Some part of the hostname contains invalid characters or is too long');
324
+ }
325
+ }
326
+ async _sendCacheRequestWithRetriesAsync(sendRequest) {
327
+ const response = await sendRequest();
328
+ const log = this._writeDebugLine.bind(this);
329
+ if (response.hasNetworkError) {
330
+ if (storageRetryOptions && storageRetryOptions.maxTries > 1) {
331
+ log('Network request failed. Will retry request as specified in storageRetryOptions');
332
+ async function retry(retryAttempt) {
333
+ const { retryDelayInMs, retryPolicyType, maxTries, maxRetryDelayInMs } = storageRetryOptions;
334
+ let delay = retryDelayInMs;
335
+ if (retryPolicyType === StorageRetryPolicyType.EXPONENTIAL) {
336
+ delay = retryDelayInMs * Math.pow(2, retryAttempt - 1);
337
+ }
338
+ delay = Math.min(maxRetryDelayInMs, delay);
339
+ log(`Will retry request in ${delay}s...`);
340
+ await Async.sleepAsync(delay);
341
+ const retryResponse = await sendRequest();
342
+ if (retryResponse.hasNetworkError) {
343
+ if (retryAttempt < maxTries - 1) {
344
+ log('The retried request failed, will try again');
345
+ return retry(retryAttempt + 1);
346
+ }
347
+ else {
348
+ log('The retried request failed and has reached the maxTries limit');
349
+ throw retryResponse.error;
350
+ }
351
+ }
352
+ return retryResponse.response;
353
+ }
354
+ return retry(1);
355
+ }
356
+ else {
357
+ log('Network request failed and storageRetryOptions is not specified');
358
+ throw response.error;
359
+ }
360
+ }
361
+ return response.response;
362
+ }
363
+ }
364
+ //# sourceMappingURL=AmazonS3Client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AmazonS3Client.js","sourceRoot":"","sources":["../src/AmazonS3Client.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as crypto from 'node:crypto';\n\nimport { Async } from '@rushstack/node-core-library';\nimport { Colorize, type ITerminal } from '@rushstack/terminal';\nimport {\n type IGetFetchOptions,\n type IFetchOptionsWithBody,\n type IWebClientResponse,\n type WebClient,\n AUTHORIZATION_HEADER_NAME\n} from '@rushstack/rush-sdk/lib/utilities/WebClient';\n\nimport type { IAmazonS3BuildCacheProviderOptionsAdvanced } from './AmazonS3BuildCacheProvider';\nimport { type IAmazonS3Credentials, fromRushEnv } from './AmazonS3Credentials';\n\nconst CONTENT_HASH_HEADER_NAME: 'x-amz-content-sha256' = 'x-amz-content-sha256';\nconst DATE_HEADER_NAME: 'x-amz-date' = 'x-amz-date';\nconst HOST_HEADER_NAME: 'host' = 'host';\nconst SECURITY_TOKEN_HEADER_NAME: 'x-amz-security-token' = 'x-amz-security-token';\n\ninterface IIsoDateString {\n date: string;\n dateTime: string;\n}\n\ntype RetryableRequestResponse<T> =\n | {\n hasNetworkError: true;\n error: Error;\n }\n | {\n hasNetworkError: false;\n response: T;\n };\n\nconst protocolRegex: RegExp = /^https?:\\/\\//;\nconst portRegex: RegExp = /:(\\d{1,5})$/;\n\n// Similar to https://docs.microsoft.com/en-us/javascript/api/@azure/storage-blob/storageretrypolicytype?view=azure-node-latest\nenum StorageRetryPolicyType {\n EXPONENTIAL = 0,\n FIXED = 1\n}\n\n// Similar to https://docs.microsoft.com/en-us/javascript/api/@azure/storage-blob/storageretryoptions?view=azure-node-latest\ninterface IStorageRetryOptions {\n maxRetryDelayInMs: number;\n maxTries: number;\n retryDelayInMs: number;\n retryPolicyType: StorageRetryPolicyType;\n}\n\nconst storageRetryOptions: IStorageRetryOptions = {\n maxRetryDelayInMs: 120 * 1000,\n maxTries: 4,\n retryDelayInMs: 4 * 1000,\n retryPolicyType: StorageRetryPolicyType.EXPONENTIAL\n};\n\n/**\n * A helper for reading and updating objects on Amazon S3\n *\n * @public\n */\nexport class AmazonS3Client {\n private readonly _credentials: IAmazonS3Credentials | undefined;\n private readonly _s3Endpoint: string;\n private readonly _s3Region: string;\n\n private readonly _webClient: WebClient;\n\n private readonly _terminal: ITerminal;\n\n public constructor(\n credentials: IAmazonS3Credentials | undefined,\n options: IAmazonS3BuildCacheProviderOptionsAdvanced,\n webClient: WebClient,\n terminal: ITerminal\n ) {\n this._credentials = credentials;\n this._terminal = terminal;\n\n this._validateEndpoint(options.s3Endpoint);\n\n this._s3Endpoint = options.s3Endpoint;\n this._s3Region = options.s3Region;\n\n this._webClient = webClient;\n }\n\n // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#create-signature-presign-entire-payload\n // We want to keep all slashes non encoded\n public static UriEncode(input: string): string {\n let output: string = '';\n for (let i: number = 0; i < input.length; i += 1) {\n const ch: string = input[i];\n if (ch.match(/[A-Za-z0-9~._-]|\\//)) {\n output += ch;\n } else {\n if (ch === ' ') {\n output += '%20';\n } else {\n output += `%${ch.charCodeAt(0).toString(16).toUpperCase()}`;\n }\n }\n }\n return output;\n }\n\n public static tryDeserializeCredentials(\n credentialString: string | undefined\n ): IAmazonS3Credentials | undefined {\n return fromRushEnv(credentialString);\n }\n\n public async getObjectAsync(objectName: string): Promise<Buffer | undefined> {\n this._writeDebugLine('Reading object from S3');\n return await this._sendCacheRequestWithRetriesAsync(async () => {\n const response: IWebClientResponse = await this._makeRequestAsync('GET', objectName);\n if (response.ok) {\n return {\n hasNetworkError: false,\n response: await response.getBufferAsync()\n };\n } else if (response.status === 404) {\n return {\n hasNetworkError: false,\n response: undefined\n };\n } else if (\n (response.status === 400 || response.status === 401 || response.status === 403) &&\n !this._credentials\n ) {\n // unauthorized due to not providing credentials,\n // silence error for better DX when e.g. running locally without credentials\n this._writeWarningLine(\n `No credentials found and received a ${response.status}`,\n ' response code from the cloud storage.',\n ' Maybe run rush update-cloud-credentials',\n ' or set the RUSH_BUILD_CACHE_CREDENTIAL env'\n );\n return {\n hasNetworkError: false,\n response: undefined\n };\n } else if (response.status === 400 || response.status === 401 || response.status === 403) {\n throw await this._getS3ErrorAsync(response);\n } else {\n const error: Error = await this._getS3ErrorAsync(response);\n return {\n hasNetworkError: true,\n error\n };\n }\n });\n }\n\n public async uploadObjectAsync(objectName: string, objectBuffer: Buffer): Promise<void> {\n if (!this._credentials) {\n throw new Error('Credentials are required to upload objects to S3.');\n }\n\n await this._sendCacheRequestWithRetriesAsync(async () => {\n const response: IWebClientResponse = await this._makeRequestAsync('PUT', objectName, objectBuffer);\n if (!response.ok) {\n return {\n hasNetworkError: true,\n error: await this._getS3ErrorAsync(response)\n };\n }\n return {\n hasNetworkError: false,\n response: undefined\n };\n });\n }\n\n private _writeDebugLine(...messageParts: string[]): void {\n // if the terminal has been closed then don't bother sending a debug message\n try {\n this._terminal.writeDebugLine(...messageParts);\n } catch (err) {\n // ignore error\n }\n }\n\n private _writeWarningLine(...messageParts: string[]): void {\n // if the terminal has been closed then don't bother sending a warning message\n try {\n this._terminal.writeWarningLine(...messageParts);\n } catch (err) {\n // ignore error\n }\n }\n\n private async _makeRequestAsync(\n verb: 'GET' | 'PUT',\n objectName: string,\n body?: Buffer\n ): Promise<IWebClientResponse> {\n const isoDateString: IIsoDateString = this._getIsoDateString();\n const bodyHash: string = this._getSha256(body);\n const headers: Record<string, string> = {};\n headers[DATE_HEADER_NAME] = isoDateString.dateTime;\n headers[CONTENT_HASH_HEADER_NAME] = bodyHash;\n\n // the host can be e.g. https://s3.aws.com or http://localhost:9000\n const host: string = this._s3Endpoint.replace(protocolRegex, '');\n const canonicalUri: string = AmazonS3Client.UriEncode(`/${objectName}`);\n this._writeDebugLine(Colorize.bold('Canonical URI: '), canonicalUri);\n\n if (this._credentials) {\n // Compute the authorization header. See https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html\n const canonicalHeaders: string[] = [\n `${HOST_HEADER_NAME}:${host}`,\n `${CONTENT_HASH_HEADER_NAME}:${bodyHash}`,\n `${DATE_HEADER_NAME}:${isoDateString.dateTime}`\n ];\n\n // Handle signing with temporary credentials (via sts:assume-role)\n if (this._credentials.sessionToken) {\n canonicalHeaders.push(`${SECURITY_TOKEN_HEADER_NAME}:${this._credentials.sessionToken}`);\n }\n\n // the canonical headers must be sorted by header name\n canonicalHeaders.sort((aHeader, bHeader) => {\n const aHeaderName: string = aHeader.split(':')[0];\n const bHeaderName: string = bHeader.split(':')[0];\n if (aHeaderName < bHeaderName) {\n return -1;\n }\n if (aHeaderName > bHeaderName) {\n return 1;\n }\n return 0;\n });\n\n // the singed header names are derived from the canonicalHeaders\n const signedHeaderNamesString: string = canonicalHeaders\n .map((header) => {\n const headerName: string = header.split(':')[0];\n return headerName;\n })\n .join(';');\n\n // The canonical request looks like this:\n // GET\n // /test.txt\n //\n // host:examplebucket.s3.amazonaws.com\n // range:bytes=0-9\n // x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n // x-amz-date:20130524T000000Z\n //\n // host;range;x-amz-content-sha256;x-amz-date\n // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n const canonicalRequest: string = [\n verb,\n canonicalUri,\n '', // we don't use query strings for these requests\n ...canonicalHeaders,\n '',\n signedHeaderNamesString,\n bodyHash\n ].join('\\n');\n const canonicalRequestHash: string = this._getSha256(canonicalRequest);\n\n const scope: string = `${isoDateString.date}/${this._s3Region}/s3/aws4_request`;\n // The string to sign looks like this:\n // AWS4-HMAC-SHA256\n // 20130524T423589Z\n // 20130524/us-east-1/s3/aws4_request\n // 7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972\n const stringToSign: string = [\n 'AWS4-HMAC-SHA256',\n isoDateString.dateTime,\n scope,\n canonicalRequestHash\n ].join('\\n');\n\n const dateKey: Buffer = this._getSha256Hmac(\n `AWS4${this._credentials.secretAccessKey}`,\n isoDateString.date\n );\n const dateRegionKey: Buffer = this._getSha256Hmac(dateKey, this._s3Region);\n const dateRegionServiceKey: Buffer = this._getSha256Hmac(dateRegionKey, 's3');\n const signingKey: Buffer = this._getSha256Hmac(dateRegionServiceKey, 'aws4_request');\n const signature: string = this._getSha256Hmac(signingKey, stringToSign, 'hex');\n\n const authorizationHeader: string = `AWS4-HMAC-SHA256 Credential=${this._credentials.accessKeyId}/${scope},SignedHeaders=${signedHeaderNamesString},Signature=${signature}`;\n\n headers[AUTHORIZATION_HEADER_NAME] = authorizationHeader;\n if (this._credentials.sessionToken) {\n // Handle signing with temporary credentials (via sts:assume-role)\n headers['X-Amz-Security-Token'] = this._credentials.sessionToken;\n }\n }\n\n const webFetchOptions: IGetFetchOptions | IFetchOptionsWithBody = {\n verb,\n headers\n };\n if (verb === 'PUT') {\n (webFetchOptions as IFetchOptionsWithBody).body = body;\n }\n\n const url: string = `${this._s3Endpoint}${canonicalUri}`;\n\n this._writeDebugLine(Colorize.bold(Colorize.underline('Sending request to S3')));\n this._writeDebugLine(Colorize.bold('HOST: '), url);\n this._writeDebugLine(Colorize.bold('Headers: '));\n for (const [name, value] of Object.entries(headers)) {\n this._writeDebugLine(Colorize.cyan(`\\t${name}: ${value}`));\n }\n\n const response: IWebClientResponse = await this._webClient.fetchAsync(url, webFetchOptions);\n\n return response;\n }\n\n public _getSha256Hmac(key: string | Buffer, data: string): Buffer;\n public _getSha256Hmac(key: string | Buffer, data: string, encoding: 'hex'): string;\n public _getSha256Hmac(key: string | Buffer, data: string, encoding?: 'hex'): Buffer | string {\n const hash: crypto.Hmac = crypto.createHmac('sha256', key);\n hash.update(data);\n if (encoding) {\n return hash.digest(encoding);\n } else {\n return hash.digest();\n }\n }\n\n private _getSha256(data?: string | Buffer): string {\n if (data) {\n const hash: crypto.Hash = crypto.createHash('sha256');\n hash.update(data);\n return hash.digest('hex');\n } else {\n // This is the null SHA256 hash\n return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';\n }\n }\n\n private _getIsoDateString(date: Date = new Date()): IIsoDateString {\n let dateString: string = date.toISOString();\n dateString = dateString.replace(/[-:]/g, ''); // Remove separator characters\n dateString = dateString.substring(0, 15); // Drop milliseconds\n\n // dateTime is an ISO8601 date. It looks like \"20130524T423589\"\n // date is an ISO date. It looks like \"20130524\"\n return {\n dateTime: `${dateString}Z`,\n date: dateString.substring(0, 8)\n };\n }\n\n private async _safeReadResponseTextAsync(response: IWebClientResponse): Promise<string | undefined> {\n try {\n return await response.getTextAsync();\n } catch (err) {\n // ignore the error\n }\n return undefined;\n }\n\n private async _getS3ErrorAsync(response: IWebClientResponse): Promise<Error> {\n const text: string | undefined = await this._safeReadResponseTextAsync(response);\n return new Error(\n `Amazon S3 responded with status code ${response.status} (${response.statusText})${\n text ? `\\n${text}` : ''\n }`\n );\n }\n\n /**\n * Validates a S3 endpoint which is http(s):// + hostname + port. Hostname validated according to RFC 1123\n * {@link https://docs.aws.amazon.com/general/latest/gr/s3.html}\n */\n private _validateEndpoint(s3Endpoint: string): void {\n let host: string = s3Endpoint;\n\n if (!s3Endpoint) {\n throw new Error('A S3 endpoint must be provided');\n }\n\n if (!s3Endpoint.match(protocolRegex)) {\n throw new Error('The S3 endpoint must start with https:// or http://');\n }\n\n host = host.replace(protocolRegex, '');\n\n if (host.match(/\\//)) {\n throw new Error('The path should be omitted from the endpoint. Use s3Prefix to specify a path');\n }\n\n const portMatch: RegExpMatchArray | null = s3Endpoint.match(portRegex);\n if (portMatch) {\n const port: number = Number(portMatch[1]);\n if (Number.isNaN(port) || port > 65535) {\n throw new Error(`Port: ${port} is an invalid port number`);\n }\n host = host.replace(portRegex, '');\n }\n\n if (host.endsWith('.')) {\n host = host.slice(0, host.length - 1);\n }\n\n if (host.length > 253) {\n throw new Error(\n 'The S3 endpoint is too long. RFC 1123 specifies a hostname should be no longer than 253 characters.'\n );\n }\n\n const subDomains: string[] = host.split('.');\n\n const subDomainRegex: RegExp = /^[a-zA-Z0-9-]+$/;\n const isValid: boolean = subDomains.every((subDomain) => {\n return (\n subDomainRegex.test(subDomain) &&\n subDomain.length < 64 &&\n !subDomain.startsWith('-') &&\n !subDomain.endsWith('-')\n );\n });\n\n if (!isValid) {\n throw new Error(\n 'Invalid S3 endpoint. Some part of the hostname contains invalid characters or is too long'\n );\n }\n }\n\n private async _sendCacheRequestWithRetriesAsync<T>(\n sendRequest: () => Promise<RetryableRequestResponse<T>>\n ): Promise<T> {\n const response: RetryableRequestResponse<T> = await sendRequest();\n\n const log: (...messageParts: string[]) => void = this._writeDebugLine.bind(this);\n\n if (response.hasNetworkError) {\n if (storageRetryOptions && storageRetryOptions.maxTries > 1) {\n log('Network request failed. Will retry request as specified in storageRetryOptions');\n async function retry(retryAttempt: number): Promise<T> {\n const { retryDelayInMs, retryPolicyType, maxTries, maxRetryDelayInMs } = storageRetryOptions;\n let delay: number = retryDelayInMs;\n if (retryPolicyType === StorageRetryPolicyType.EXPONENTIAL) {\n delay = retryDelayInMs * Math.pow(2, retryAttempt - 1);\n }\n delay = Math.min(maxRetryDelayInMs, delay);\n\n log(`Will retry request in ${delay}s...`);\n await Async.sleepAsync(delay);\n const retryResponse: RetryableRequestResponse<T> = await sendRequest();\n\n if (retryResponse.hasNetworkError) {\n if (retryAttempt < maxTries - 1) {\n log('The retried request failed, will try again');\n return retry(retryAttempt + 1);\n } else {\n log('The retried request failed and has reached the maxTries limit');\n throw retryResponse.error;\n }\n }\n\n return retryResponse.response;\n }\n return retry(1);\n } else {\n log('Network request failed and storageRetryOptions is not specified');\n throw response.error;\n }\n }\n\n return response.response;\n }\n}\n"]}
@@ -0,0 +1,50 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { EnvironmentConfiguration } from '@rushstack/rush-sdk';
4
+ export const AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID';
5
+ export const AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY';
6
+ export const AWS_SESSION_TOKEN = 'AWS_SESSION_TOKEN';
7
+ /**
8
+ * Attempt to read credentials from the commonly used AWS_* env vars.
9
+ */
10
+ export const fromAmazonEnv = () => {
11
+ const accessKeyId = process.env[AWS_ACCESS_KEY_ID];
12
+ const secretAccessKey = process.env[AWS_SECRET_ACCESS_KEY];
13
+ const sessionToken = process.env[AWS_SESSION_TOKEN];
14
+ if (accessKeyId && secretAccessKey) {
15
+ return {
16
+ accessKeyId,
17
+ secretAccessKey,
18
+ sessionToken
19
+ };
20
+ }
21
+ else if (accessKeyId) {
22
+ throw new Error(`The "${AWS_ACCESS_KEY_ID}" env variable is set, but the "${AWS_SECRET_ACCESS_KEY}" ` +
23
+ `env variable is not set. Both or neither must be provided.`);
24
+ }
25
+ else if (secretAccessKey) {
26
+ throw new Error(`The "${AWS_SECRET_ACCESS_KEY}" env variable is set, but the "${AWS_ACCESS_KEY_ID}" ` +
27
+ `env variable is not set. Both or neither must be provided.`);
28
+ }
29
+ else {
30
+ return undefined;
31
+ }
32
+ };
33
+ /**
34
+ * Attempt to parse credentials set from the RUSH_BUILD_CACHE_CREDENTIAL env var.
35
+ */
36
+ export const fromRushEnv = (credential = EnvironmentConfiguration.buildCacheCredential) => {
37
+ if (!credential) {
38
+ return undefined;
39
+ }
40
+ const fields = credential.split(':');
41
+ if (fields.length < 2 || fields.length > 3) {
42
+ throw new Error(`Rush build cache credential is in an unexpected format.`);
43
+ }
44
+ return {
45
+ accessKeyId: fields[0],
46
+ secretAccessKey: fields[1],
47
+ sessionToken: fields[2]
48
+ };
49
+ };
50
+ //# sourceMappingURL=AmazonS3Credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AmazonS3Credentials.js","sourceRoot":"","sources":["../src/AmazonS3Credentials.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,CAAC,MAAM,iBAAiB,GAAwB,mBAAmB,CAAC;AAC1E,MAAM,CAAC,MAAM,qBAAqB,GAA4B,uBAAuB,CAAC;AACtF,MAAM,CAAC,MAAM,iBAAiB,GAAwB,mBAAmB,CAAC;AAa1E;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAqC,EAAE;IAClE,MAAM,WAAW,GAAuB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACvE,MAAM,eAAe,GAAuB,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC/E,MAAM,YAAY,GAAuB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAExE,IAAI,WAAW,IAAI,eAAe,EAAE,CAAC;QACnC,OAAO;YACL,WAAW;YACX,eAAe;YACf,YAAY;SACb,CAAC;IACJ,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,QAAQ,iBAAiB,mCAAmC,qBAAqB,IAAI;YACnF,4DAA4D,CAC/D,CAAC;IACJ,CAAC;SAAM,IAAI,eAAe,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,QAAQ,qBAAqB,mCAAmC,iBAAiB,IAAI;YACnF,4DAA4D,CAC/D,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,UAAU,GAAG,wBAAwB,CAAC,oBAAoB,EACxB,EAAE;IACpC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAa,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1B,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;KACxB,CAAC;AACJ,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { EnvironmentConfiguration } from '@rushstack/rush-sdk';\n\nexport const AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID' = 'AWS_ACCESS_KEY_ID';\nexport const AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY' = 'AWS_SECRET_ACCESS_KEY';\nexport const AWS_SESSION_TOKEN: 'AWS_SESSION_TOKEN' = 'AWS_SESSION_TOKEN';\n\n/**\n * Credentials for authorizing and signing requests to an Amazon S3 endpoint.\n *\n * @public\n */\nexport interface IAmazonS3Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken: string | undefined;\n}\n\n/**\n * Attempt to read credentials from the commonly used AWS_* env vars.\n */\nexport const fromAmazonEnv = (): IAmazonS3Credentials | undefined => {\n const accessKeyId: string | undefined = process.env[AWS_ACCESS_KEY_ID];\n const secretAccessKey: string | undefined = process.env[AWS_SECRET_ACCESS_KEY];\n const sessionToken: string | undefined = process.env[AWS_SESSION_TOKEN];\n\n if (accessKeyId && secretAccessKey) {\n return {\n accessKeyId,\n secretAccessKey,\n sessionToken\n };\n } else if (accessKeyId) {\n throw new Error(\n `The \"${AWS_ACCESS_KEY_ID}\" env variable is set, but the \"${AWS_SECRET_ACCESS_KEY}\" ` +\n `env variable is not set. Both or neither must be provided.`\n );\n } else if (secretAccessKey) {\n throw new Error(\n `The \"${AWS_SECRET_ACCESS_KEY}\" env variable is set, but the \"${AWS_ACCESS_KEY_ID}\" ` +\n `env variable is not set. Both or neither must be provided.`\n );\n } else {\n return undefined;\n }\n};\n\n/**\n * Attempt to parse credentials set from the RUSH_BUILD_CACHE_CREDENTIAL env var.\n */\nexport const fromRushEnv = (\n credential = EnvironmentConfiguration.buildCacheCredential\n): IAmazonS3Credentials | undefined => {\n if (!credential) {\n return undefined;\n }\n\n const fields: string[] = credential.split(':');\n if (fields.length < 2 || fields.length > 3) {\n throw new Error(`Rush build cache credential is in an unexpected format.`);\n }\n\n return {\n accessKeyId: fields[0],\n secretAccessKey: fields[1],\n sessionToken: fields[2]\n };\n};\n"]}
@@ -0,0 +1,53 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ const PLUGIN_NAME = 'AmazonS3BuildCachePlugin';
4
+ /**
5
+ * @public
6
+ */
7
+ export class RushAmazonS3BuildCachePlugin {
8
+ constructor() {
9
+ this.pluginName = PLUGIN_NAME;
10
+ }
11
+ apply(rushSession, rushConfig) {
12
+ rushSession.hooks.initialize.tap(PLUGIN_NAME, () => {
13
+ rushSession.registerCloudBuildCacheProviderFactory('amazon-s3', async (buildCacheConfig) => {
14
+ const { amazonS3Configuration } = buildCacheConfig;
15
+ let options;
16
+ const { s3Endpoint, s3Bucket, s3Region } = amazonS3Configuration;
17
+ const s3Prefix = amazonS3Configuration.s3Prefix || undefined;
18
+ const isCacheWriteAllowed = !!amazonS3Configuration.isCacheWriteAllowed;
19
+ if (s3Prefix && s3Prefix[0] === '/') {
20
+ throw new Error('s3Prefix should not have a leading /');
21
+ }
22
+ // mutually exclusive
23
+ if (s3Bucket && s3Endpoint) {
24
+ throw new Error('Only one of "s3Bucket" or "s3Endpoint" must be provided.');
25
+ }
26
+ if (s3Endpoint) {
27
+ options = {
28
+ // IAmazonS3BuildCacheProviderOptionsAdvanced
29
+ s3Region,
30
+ s3Endpoint,
31
+ s3Prefix,
32
+ isCacheWriteAllowed
33
+ };
34
+ }
35
+ if (s3Bucket) {
36
+ options = {
37
+ // IAmazonS3BuildCacheProviderOptionsSimple
38
+ s3Region,
39
+ s3Bucket,
40
+ s3Prefix,
41
+ isCacheWriteAllowed
42
+ };
43
+ }
44
+ if (!options) {
45
+ throw new Error('You must provide either an s3Endpoint or s3Bucket');
46
+ }
47
+ const { AmazonS3BuildCacheProvider } = await import('./AmazonS3BuildCacheProvider');
48
+ return new AmazonS3BuildCacheProvider(options, rushSession);
49
+ });
50
+ });
51
+ }
52
+ }
53
+ //# sourceMappingURL=RushAmazonS3BuildCachePlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RushAmazonS3BuildCachePlugin.js","sourceRoot":"","sources":["../src/RushAmazonS3BuildCachePlugin.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAS3D,MAAM,WAAW,GAAW,0BAA0B,CAAC;AAgCvD;;GAEG;AACH,MAAM,OAAO,4BAA4B;IAAzC;QACS,eAAU,GAAW,WAAW,CAAC;IAqD1C,CAAC;IAnDQ,KAAK,CAAC,WAAwB,EAAE,UAA6B;QAClE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE;YACjD,WAAW,CAAC,sCAAsC,CAAC,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE;gBAIzF,MAAM,EAAE,qBAAqB,EAAE,GAAG,gBAA+B,CAAC;gBAClE,IAAI,OAGS,CAAC;gBACd,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CAAC;gBACjE,MAAM,QAAQ,GAAuB,qBAAqB,CAAC,QAAQ,IAAI,SAAS,CAAC;gBACjF,MAAM,mBAAmB,GAAY,CAAC,CAAC,qBAAqB,CAAC,mBAAmB,CAAC;gBAEjF,IAAI,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBAC1D,CAAC;gBAED,qBAAqB;gBACrB,IAAI,QAAQ,IAAI,UAAU,EAAE,CAAC;oBAC3B,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;gBAC9E,CAAC;gBAED,IAAI,UAAU,EAAE,CAAC;oBACf,OAAO,GAAG;wBACR,6CAA6C;wBAC7C,QAAQ;wBACR,UAAU;wBACV,QAAQ;wBACR,mBAAmB;qBACpB,CAAC;gBACJ,CAAC;gBACD,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO,GAAG;wBACR,2CAA2C;wBAC3C,QAAQ;wBACR,QAAQ;wBACR,QAAQ;wBACR,mBAAmB;qBACpB,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;gBACvE,CAAC;gBAED,MAAM,EAAE,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;gBACpF,OAAO,IAAI,0BAA0B,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC9D,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { IRushPlugin, RushSession, RushConfiguration } from '@rushstack/rush-sdk';\n\nimport type {\n IAmazonS3BuildCacheProviderOptionsAdvanced,\n IAmazonS3BuildCacheProviderOptionsSimple\n} from './AmazonS3BuildCacheProvider';\n\nconst PLUGIN_NAME: string = 'AmazonS3BuildCachePlugin';\n\n/**\n * @public\n */\nexport interface IAmazonS3ConfigurationJson {\n /**\n * (Required unless s3Endpoint is specified) The name of the bucket to use for build cache (e.g. \"my-bucket\").\n */\n s3Bucket?: string;\n\n /**\n * (Required unless s3Bucket is specified) The Amazon S3 endpoint of the bucket to use for build cache (e.g. \"my-bucket.s3.us-east-2.amazonaws.com\" or \"http://localhost:9000\").\n */\n s3Endpoint?: string;\n\n /**\n * The Amazon S3 region of the bucket to use for build cache (e.g. \"us-east-1\").\n */\n s3Region: string;\n\n /**\n * An optional prefix (\"folder\") for cache items.\n */\n s3Prefix?: string;\n\n /**\n * If set to true, allow writing to the cache. Defaults to false.\n */\n isCacheWriteAllowed?: boolean;\n}\n\n/**\n * @public\n */\nexport class RushAmazonS3BuildCachePlugin implements IRushPlugin {\n public pluginName: string = PLUGIN_NAME;\n\n public apply(rushSession: RushSession, rushConfig: RushConfiguration): void {\n rushSession.hooks.initialize.tap(PLUGIN_NAME, () => {\n rushSession.registerCloudBuildCacheProviderFactory('amazon-s3', async (buildCacheConfig) => {\n type IBuildCache = typeof buildCacheConfig & {\n amazonS3Configuration: IAmazonS3ConfigurationJson;\n };\n const { amazonS3Configuration } = buildCacheConfig as IBuildCache;\n let options:\n | IAmazonS3BuildCacheProviderOptionsAdvanced\n | IAmazonS3BuildCacheProviderOptionsSimple\n | undefined;\n const { s3Endpoint, s3Bucket, s3Region } = amazonS3Configuration;\n const s3Prefix: undefined | string = amazonS3Configuration.s3Prefix || undefined;\n const isCacheWriteAllowed: boolean = !!amazonS3Configuration.isCacheWriteAllowed;\n\n if (s3Prefix && s3Prefix[0] === '/') {\n throw new Error('s3Prefix should not have a leading /');\n }\n\n // mutually exclusive\n if (s3Bucket && s3Endpoint) {\n throw new Error('Only one of \"s3Bucket\" or \"s3Endpoint\" must be provided.');\n }\n\n if (s3Endpoint) {\n options = {\n // IAmazonS3BuildCacheProviderOptionsAdvanced\n s3Region,\n s3Endpoint,\n s3Prefix,\n isCacheWriteAllowed\n };\n }\n if (s3Bucket) {\n options = {\n // IAmazonS3BuildCacheProviderOptionsSimple\n s3Region,\n s3Bucket,\n s3Prefix,\n isCacheWriteAllowed\n };\n }\n if (!options) {\n throw new Error('You must provide either an s3Endpoint or s3Bucket');\n }\n\n const { AmazonS3BuildCacheProvider } = await import('./AmazonS3BuildCacheProvider');\n return new AmazonS3BuildCacheProvider(options, rushSession);\n });\n });\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ /// <reference types="node" preserve="true" />
4
+ import { RushAmazonS3BuildCachePlugin } from './RushAmazonS3BuildCachePlugin';
5
+ export { AmazonS3Client } from './AmazonS3Client';
6
+ export default RushAmazonS3BuildCachePlugin;
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,8CAA8C;AAE9C,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,eAAe,4BAA4B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/// <reference types=\"node\" preserve=\"true\" />\n\nimport { RushAmazonS3BuildCachePlugin } from './RushAmazonS3BuildCachePlugin';\n\nexport { type IAmazonS3Credentials } from './AmazonS3Credentials';\nexport { AmazonS3Client } from './AmazonS3Client';\nexport default RushAmazonS3BuildCachePlugin;\nexport type {\n IAmazonS3BuildCacheProviderOptionsBase,\n IAmazonS3BuildCacheProviderOptionsAdvanced,\n IAmazonS3BuildCacheProviderOptionsSimple\n} from './AmazonS3BuildCacheProvider';\n"]}
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Configuration for build cache with Amazon S3 configuration",
4
+ "type": "object",
5
+ "oneOf": [
6
+ {
7
+ "type": "object",
8
+ "required": ["s3Endpoint", "s3Region"],
9
+ "properties": {
10
+ "s3Endpoint": {
11
+ "type": "string",
12
+ "description": "(Required) The Amazon S3 endpoint of the bucket to use for build cache (e.g. \"s3.us-east-2.amazonaws.com\")."
13
+ },
14
+ "s3Region": {
15
+ "type": "string",
16
+ "description": "(Required) The Amazon S3 region of the bucket to use for build cache (e.g. \"us-east-1\")."
17
+ },
18
+ "s3Prefix": {
19
+ "type": "string",
20
+ "description": "An optional prefix (\"folder\") for cache items."
21
+ },
22
+ "isCacheWriteAllowed": {
23
+ "type": "boolean",
24
+ "description": "If set to true, allow writing to the cache. Defaults to false."
25
+ }
26
+ }
27
+ },
28
+ {
29
+ "type": "object",
30
+ "required": ["s3Bucket", "s3Region"],
31
+ "properties": {
32
+ "s3Bucket": {
33
+ "type": "string",
34
+ "description": "(Required unless s3Endpoint is specified) The name of the bucket to use for build cache (e.g. \"my-bucket\")."
35
+ },
36
+ "s3Region": {
37
+ "type": "string",
38
+ "description": "(Required) The Amazon S3 region of the bucket to use for build cache (e.g. \"us-east-1\")."
39
+ },
40
+ "s3Prefix": {
41
+ "type": "string",
42
+ "description": "An optional prefix (\"folder\") for cache items."
43
+ },
44
+ "isCacheWriteAllowed": {
45
+ "type": "boolean",
46
+ "description": "If set to true, allow writing to the cache. Defaults to false."
47
+ }
48
+ }
49
+ }
50
+ ]
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/rush-amazon-s3-build-cache-plugin",
3
- "version": "5.168.0",
3
+ "version": "5.169.1",
4
4
  "description": "Rush plugin for Amazon S3 cloud build cache",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,22 +8,46 @@
8
8
  "directory": "rush-plugins/rush-amazon-s3-build-cache-plugin"
9
9
  },
10
10
  "homepage": "https://rushjs.io",
11
- "main": "lib/index.js",
12
- "types": "lib/index.d.ts",
11
+ "main": "./lib-commonjs/index.js",
12
+ "module": "./lib-esm/index.js",
13
+ "types": "./lib-dts/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./lib-dts/index.d.ts",
17
+ "import": "./lib-esm/index.js",
18
+ "require": "./lib-commonjs/index.js"
19
+ },
20
+ "./lib/*.schema.json": "./lib-commonjs/*.schema.json",
21
+ "./lib/*": {
22
+ "types": "./lib-dts/*.d.ts",
23
+ "import": "./lib-esm/*.js",
24
+ "require": "./lib-commonjs/*.js"
25
+ },
26
+ "./rush-plugin-manifest.json": "./rush-plugin-manifest.json",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "typesVersions": {
30
+ "*": {
31
+ "lib/*": [
32
+ "lib-dts/*"
33
+ ]
34
+ }
35
+ },
13
36
  "license": "MIT",
14
37
  "dependencies": {
15
38
  "https-proxy-agent": "~5.0.0",
16
- "@rushstack/node-core-library": "5.19.1",
17
- "@rushstack/rush-sdk": "5.168.0",
18
- "@rushstack/credential-cache": "0.1.11",
19
- "@rushstack/terminal": "0.21.0"
39
+ "@rushstack/credential-cache": "0.2.0",
40
+ "@rushstack/node-core-library": "5.20.0",
41
+ "@rushstack/terminal": "0.22.0",
42
+ "@rushstack/rush-sdk": "5.169.1"
20
43
  },
21
44
  "devDependencies": {
22
45
  "eslint": "~9.37.0",
23
- "@microsoft/rush-lib": "5.168.0",
24
- "@rushstack/heft": "1.1.14",
46
+ "@microsoft/rush-lib": "5.169.1",
47
+ "@rushstack/heft": "1.2.0",
25
48
  "local-node-rig": "1.0.0"
26
49
  },
50
+ "sideEffects": false,
27
51
  "scripts": {
28
52
  "build": "heft build --clean",
29
53
  "start": "heft test --clean --watch",
@@ -4,8 +4,8 @@
4
4
  {
5
5
  "pluginName": "rush-amazon-s3-build-cache-plugin",
6
6
  "description": "Rush plugin for Amazon S3 cloud build cache",
7
- "entryPoint": "lib/index.js",
8
- "optionsSchema": "lib/schemas/amazon-s3-config.schema.json"
7
+ "entryPoint": "./lib-commonjs/index.js",
8
+ "optionsSchema": "./lib-commonjs/schemas/amazon-s3-config.schema.json"
9
9
  }
10
10
  ]
11
11
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes