@linked-helper/framework.build-tools.aws-artifacts
Version:
LH framework build tools: upload artifacts
177 lines (173 loc) • 6.03 kB
JavaScript
import { ListObjectsCommand, S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import _glob from 'glob';
import fs, { promises } from 'node:fs';
import path from 'node:path';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { g as getS3Client, c as IPlatform } from './uploadFiles-OK2kqTja.mjs';
async function downloadFiles({ prefix, root, bucket, files, platform, ...client }, { onListFiles, onFileDownloaded, onFileDownloadFailed, onDownloaded, } = {}) {
const glob = (() => {
return (pattern) => _glob(pattern, {
cwd: root,
nodir: true,
absolute: true,
});
})();
const s3 = getS3Client(client);
const listResult = await s3.send(new ListObjectsCommand({
Bucket: bucket,
Prefix: prefix + '/',
}));
const entries = listResult.Contents;
if (!entries) {
throw new Error(`no objects found with prefix "${prefix}/"`);
}
const platforms = platform === 'all'
? IPlatform.all
: [platform];
const fileInfos = [];
// filter files to download
{
const entriesPathsByKey = new Map(entries.map(e => [e.Key, path.resolve(root, e.Key.replace(prefix + '/', ''))]));
// first create empty files as in s3 bucket
// and wait for them to be created
await Promise.all([...entriesPathsByKey.values()].map(async (entryPath, i) => {
const delay = (i % 10) * 16;
await sleep(delay);
await touchFile(entryPath, 200 + delay);
}));
// match entries by glob patterns
// and remove matched from entries map
for (const p of [...platforms, 'all']) {
for (const pattern of files[p]) {
for (const filePath of await glob(pattern)) {
const key = path.join(prefix, path.relative(root, filePath)).split(path.sep).join(path.posix.sep);
if (entriesPathsByKey.has(key)) {
fileInfos.push({
path: filePath,
relativePath: path.relative(root, filePath).split(path.sep).join(path.posix.sep),
key,
});
entriesPathsByKey.delete(key);
}
}
}
}
// delete unused empty files
await Promise.all([...entriesPathsByKey.values()].map(async (entryPath, i) => {
const delay = (i % 10) * 8;
await sleep(delay);
await deleteFile(entryPath, 200 + delay);
}));
}
try {
onListFiles?.(fileInfos);
}
catch (dontCare) { /* do nothing */ }
if (!fileInfos.length) {
throw new Error('no files found');
}
const downloaded = [];
await Promise.all(fileInfos.map(async (file) => {
try {
await promises.mkdir(path.dirname(file.path), { recursive: true });
await promisify(pipeline)(await download({ s3, bucket, key: file.key }), fs.createWriteStream(file.path));
downloaded.push(file);
try {
onFileDownloaded?.(file, [...downloaded], fileInfos);
}
catch (dontCare) { /* do nothing */ }
}
catch (err) {
try {
onFileDownloadFailed?.(file, err, [...downloaded], fileInfos);
}
catch (dontCare) { /* do nothing */ }
throw err;
}
}));
try {
onDownloaded?.(fileInfos);
}
catch (dontCare) { /* do nothing */ }
return downloaded;
}
async function download({ bucket, key, ...client }) {
const s3 = client.s3 ?? new S3Client(client);
const result = await s3.send(new GetObjectCommand({
Bucket: bucket,
Key: key,
}));
return result.Body;
}
async function touchFile(filePath, delayBetweenTries = 300, maxTries = 10) {
await promises.mkdir(path.dirname(filePath), { recursive: true });
await promises.writeFile(filePath, '');
let tries = 0;
while (true) {
try {
await promises.access(filePath, fs.constants.W_OK);
break;
}
catch (e) {
if (++tries >= maxTries) {
throw e;
}
await sleep(delayBetweenTries);
}
}
}
async function deleteFile(filePath, delayBetweenTries = 300, maxTries = 10) {
await promises.unlink(filePath);
let tries = 0;
while (true) {
try {
await promises.access(filePath, fs.constants.F_OK);
}
catch (e) {
if (e && e.errno === 'ENOENT') {
break;
}
}
if (++tries >= maxTries) {
// tslint:disable-next-line: no-console
console.warn('failed to delete', filePath);
break;
}
await sleep(delayBetweenTries);
}
}
async function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
function isDependency({ cwd = process.cwd() } = {}) {
const parts = path.resolve(cwd).split(path.sep);
// tslint:disable-next-line: no-string-literal
const packageName = process.env['npm_package_name'];
if (packageName) {
const [, scope, name] = /^(@.*)\/(.*)$/.exec(packageName) ?? [];
if (scope && name) {
if (parts[parts.length - 1] === name) {
parts.pop();
}
if (parts[parts.length - 1] === scope) {
parts.pop();
}
}
else {
if (parts[parts.length - 1] === packageName) {
parts.pop();
}
}
}
const parent1Folder = parts.pop();
const parent2Folder = parts.pop();
if (parent1Folder === 'node_modules') {
return true;
}
else if (parent1Folder && /^@/.test(parent1Folder) && parent2Folder === 'node_modules') {
return true;
}
return false;
}
export { downloadFiles as d, isDependency as i };