UNPKG

@salesforce/plugin-release-management

Version:
153 lines 5.86 kB
/* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import os from 'node:os'; import path from 'node:path'; import fs from 'node:fs'; import { ux } from '@oclif/core'; import got from 'got'; import { SfError } from '@salesforce/core'; import chalk from 'chalk'; import { S3 } from '@aws-sdk/client-s3'; import { NodeHttpHandler } from '@smithy/node-http-handler'; import { isString } from '@salesforce/ts-types'; import { api } from './codeSigning/packAndSign.js'; const BASE_URL = 'https://developer.salesforce.com'; const BUCKET = 'dfc-data-production'; export class AmazonS3 { options; static STATUS_URL = 'https://s3.amazonaws.com'; directory; s3; baseKey; constructor(options) { this.options = options; this.directory = `https://developer.salesforce.com/media/salesforce-cli/${this.options.cli ?? ''}`; this.baseKey = this.directory.replace(BASE_URL, '').replace(/^\//, ''); this.s3 = new S3({ region: 'us-east-1', ...resolveCredentials(options.credentials), requestHandler: buildRequestHandler(), }); } // there's an abstract class for cli:install:test using this // eslint-disable-next-line class-methods-use-this async ping() { const { statusCode } = await got.get(AmazonS3.STATUS_URL); return { service: 'Amazon S3', available: statusCode >= 200 && statusCode < 300 }; } async getManifestFromChannel(channel) { const url = `${this.directory}/channels/${channel}/${this.options.cli}-darwin-x64-buildmanifest`; const filename = await getFileAtUrl(url); return JSON.parse(fs.readFileSync(filename, 'utf8')); } async getObject(options) { return this.s3.getObject({ ...options, Key: options.Key?.replace(BASE_URL, '').replace(/^\//, ''), ...{ Bucket: this.options.bucket ?? BUCKET }, }); } // Paginates listObjectV2 and returns both Contents and CommonPrefixes async listAllObjects(key) { const prefix = key.startsWith(this.baseKey) ? key : `${this.baseKey}/${key}/`; const bucket = this.options.bucket ?? BUCKET; let continuationToken; const allContents = []; const allCommonPrefixes = []; // Use maximum iteration to ensure termination const MAX_ITERATIONS = 100; for (let i = 1; i <= MAX_ITERATIONS; i++) { // eslint-disable-next-line no-await-in-loop const response = await this.s3.listObjectsV2({ Bucket: bucket, Delimiter: '/', Prefix: prefix, ContinuationToken: continuationToken, }); if (response.Contents) { allContents.push(...response.Contents); } if (response.CommonPrefixes) { allCommonPrefixes.push(...response.CommonPrefixes.map((item) => item.Prefix).filter(isString)); } if (!response.IsTruncated) break; if (i === MAX_ITERATIONS) throw new SfError('Max listObjectsV2 iterations reached'); continuationToken = response.NextContinuationToken; } return { contents: allContents, commonPrefixes: allCommonPrefixes }; } async listCommonPrefixes(key) { const result = await this.listAllObjects(key); return result.commonPrefixes; } async listKeyContents(key) { const result = await this.listAllObjects(key); return result.contents; } } export const download = async (url, location, silent = false) => { const downloadStream = got.stream(url); const fileWriterStream = fs.createWriteStream(location); return new Promise((resolve) => { downloadStream.on('error', (error) => { if (!silent) ux.error(`Download failed: ${error.message}`); }); fileWriterStream .on('error', (error) => { if (!silent) ux.action.stop('Failed'); if (!silent) ux.error(`Could not write file to system: ${error.message}`); }) .on('finish', () => { if (!silent) ux.action.stop(); resolve(); }); if (!silent) ux.action.start(`Downloading ${chalk.cyan(url)}`); downloadStream.pipe(fileWriterStream); }); }; const getFileAtUrl = async (url) => { const availability = await fileIsAvailable(url); if (availability.available) { const filename = path.join(os.tmpdir(), `file${Math.random()}`); await download(url, filename, true); return filename; } else { throw new SfError(`File at url: ${url} does not exist`); } }; const resolveCredentials = (credentialOptions) => { if (credentialOptions) { return { credentials: credentialOptions }; } return process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY ? { credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY }, } : {}; }; const fileIsAvailable = async (url) => { const { statusCode } = await got.head(url, { throwHttpErrors: false }); return { service: 'file', name: url, available: statusCode >= 200 && statusCode < 300 }; }; const buildRequestHandler = () => { const agent = api.getAgentForUri('https://s3.amazonaws.com'); const options = agent && agent.http ? { httpAgent: agent.http, } : {}; return new NodeHttpHandler(options); }; //# sourceMappingURL=amazonS3.js.map