UNPKG

@linked-helper/framework.build-tools.aws-artifacts

Version:

LH framework build tools: upload artifacts

177 lines (173 loc) 6.03 kB
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 };