@harboor/core
Version:
231 lines (225 loc) • 8.41 kB
JavaScript
import * as path from 'node:path';
import path__default from 'node:path';
import { access, constants, readFile, mkdir, copyFile, writeFile } from 'node:fs/promises';
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
import * as os from 'node:os';
async function fileFinder(possibleFileNames, possiblePaths) {
const possibleAbsPaths = possiblePaths.map(_p => _p.startsWith("/") ? _p : path__default.resolve(process.cwd(), _p));
const possibleFiles = createLoadingOrder(possibleFileNames, possibleAbsPaths);
return findFile(possibleFiles);
}
function createLoadingOrder(filenames, possibleAbsPaths) {
return possibleAbsPaths.reduce((memo, _path) => {
memo = memo.concat(filenames.map(_name => path__default.resolve(_path, _name)));
return memo;
}, []);
}
async function findFile(possibleFiles) {
for await (const file of possibleFiles) {
if (await verifyFile(file)) {
return file;
}
}
return null;
}
async function verifyFile(_filepath) {
try {
await access(_filepath, constants.R_OK);
return true;
} catch (e) {
return false;
}
}
async function readTsConfig(possiblePaths, possibleFileNames = ["tsconfig.json"]) {
const location = await fileFinder(possibleFileNames, possiblePaths);
if (!location) return null;
return JSON.parse(await readFile(location, "utf-8"));
}
function isAliasPath(filepath, tsconfig) {
const aliases = findAliases(tsconfig);
return Object.keys(aliases).some(pattern => filepath.startsWith(pattern.replace("*", "")));
}
function resolveAlias(alias, tsconfig, projectPath) {
const _searchPath = projectPath ?? process.cwd();
const aliases = findAliases(tsconfig);
const pattern = Object.keys(aliases).find(_pattern => alias.startsWith(_pattern.replace("*", "")));
if (!pattern.includes("*")) {
return aliases[pattern].map(p => path__default.resolve(_searchPath, p));
}
const _input = alias.replace(pattern.replace("*", ""), "");
return aliases[pattern].map(p => path__default.resolve(_searchPath, p.replace("*", _input)));
}
function findAliases(tsconfig) {
return tsconfig ? tsconfig?.compilerOptions?.paths ?? {} : {};
}
function pathv(relativePath) {
return relativePath;
}
const rePathvCalls = `(pathv\\(('|")(.*)('|"))\\)`;
const rePathvCallsGM = new RegExp(rePathvCalls, "gm");
const rePathvCallsNoFlag = new RegExp(rePathvCalls, "");
function hasPathvCalls(content) {
return rePathvCallsGM.test(content);
}
function findProjectPath(sourceRelativePath) {
const _src = path__default.normalize(sourceRelativePath);
const cwd = process.cwd();
return cwd.endsWith(_src) ? path__default.resolve(cwd, _src.split("/").map(() => "..").join("/")) : cwd;
}
async function processPathvCalls(content, fileAbsPath, sourceRelativePath, distRelativePath) {
const projectPath = findProjectPath(sourceRelativePath);
const tsconfig = await readTsConfig([path__default.resolve(projectPath, sourceRelativePath), projectPath]);
const analysis = analysePaths(content, fileAbsPath, projectPath, tsconfig, sourceRelativePath, distRelativePath);
await distAnalysedFiles(analysis, projectPath);
return formatSourceContent(content, analysis);
}
function formatSourceContent(content, analysis) {
return Object.keys(analysis).reduce((memo, token) => memo.replace(token, analysis[token].pathvExp), `import * as path from 'node:path';` + content);
}
async function distAnalysedFiles(analysis, projectPath) {
for await (const {
realRelPath,
relDistPath
} of Object.values(analysis)) {
const fileDistDir = path__default.resolve(projectPath, path__default.dirname(relDistPath));
await mkdir(fileDistDir, {
recursive: true
});
await copyFile(path__default.resolve(projectPath, realRelPath), path__default.resolve(projectPath, relDistPath));
}
}
function analysePaths(content, fileAbsPath, projectPath, tsconfig, sourceRelativePath, distRelativePath) {
const matches = content.match(rePathvCallsGM);
if (!matches) {
return {};
}
const matchesFormatted = matches.map(text => text.match(rePathvCallsNoFlag));
const result = {};
for (const _matches of matchesFormatted) {
if (!_matches) continue;
const input = _matches[3];
const inputType = findInputType(input);
const realRelPath = findRealRelativePath(input, inputType);
const relDistPath = findCorrespondingDistPath(realRelPath, inputType);
result[_matches[0]] = {
realRelPath,
relDistPath,
pathvExp: `path.resolve(import.meta.dirname, '${"./" + path__default.relative(distRelativePath, relDistPath)}')`
};
}
return result;
function findRealRelativePath(input, inputType) {
switch (inputType) {
case "PROJECT_RELATIVE_OUTSIDE_SOURCE":
return input;
case "PROJECT_RELATIVE_IN_SOURCE":
return input;
case "ALIAS":
const resolved = resolveAlias(input, tsconfig, projectPath)[0];
return "./" + path__default.relative(projectPath, resolved);
case "ABSOLUTE":
return input.includes(projectPath) ? path__default.relative(path__default.resolve(projectPath, sourceRelativePath), input) : input;
}
}
function findCorrespondingDistPath(projectRelativeFilePath, inputType) {
if (projectRelativeFilePath.startsWith("/")) {
return path__default.join(projectPath, distRelativePath, projectRelativeFilePath);
}
if (inputType === "PROJECT_RELATIVE_OUTSIDE_SOURCE") {
return "./" + path__default.join(distRelativePath, projectRelativeFilePath);
}
return "./" + path__default.join(distRelativePath, path__default.relative(sourceRelativePath, projectRelativeFilePath));
}
function findInputType(input) {
if (input.startsWith("/")) {
return "ABSOLUTE";
} else if (tsconfig && isAliasPath(input, tsconfig)) {
return "ALIAS";
} else if (path__default.normalize(input).includes(path__default.normalize(sourceRelativePath))) {
return "PROJECT_RELATIVE_IN_SOURCE";
} else {
return "PROJECT_RELATIVE_OUTSIDE_SOURCE";
}
}
}
async function fetchSecretsAws({
aws,
dest
}) {
const {
credentials,
secretName
} = aws;
const {
region,
accessKey,
accessKeySecret
} = credentials;
const client = new SecretsManagerClient({
region,
credentials: {
accessKeyId: accessKey,
secretAccessKey: accessKeySecret
}
});
let response;
try {
response = await client.send(new GetSecretValueCommand({
SecretId: secretName
}));
} catch (e) {
return e;
}
if (!response.SecretString) {
return new Error("No text found in the response.");
}
const secrets = JSON.parse(response.SecretString);
const resolved = resolveReferencesRecursive(secrets);
if (dest) {
await save(dest, secrets);
}
return resolved;
}
function resolveReferencesRecursive(secrets, hadAtLeastOneReference = true) {
if (!hadAtLeastOneReference) {
return secrets;
}
const re = /\${([^}]*)}/g;
const keys = Object.keys(secrets);
const resolved = {};
let references = 0;
for (const key of keys) {
const v = secrets[key];
const matches = [...v.matchAll(re)];
if (matches.length > 0) references += matches.filter(m => m[1] in secrets).length;
const resolvedValue = matches.reduce((memo, m) => m[1] in secrets ? memo.replace(m[0], secrets[m[1]]) : memo, v);
resolved[key] = resolvedValue;
}
return resolveReferencesRecursive(resolved, references > 0);
}
async function save(dest, secrets) {
const _dest = path.isAbsolute(dest) ? dest : path.resolve(process.cwd(), dest);
const dir = path.dirname(_dest);
await mkdir(dir, {
recursive: true
});
const ext = path.extname(_dest);
const format = ext === ".env" ? "dotenv" : ext === ".sh" ? "shell" : "dotenv";
const data = Object.keys(secrets).reduce((memo, name) => memo + processPair(format, name, secrets[name]), "");
await writeFile(_dest, data, {
mode: process.env.VITEST ? 0o777 : 0o455
});
return true;
function processPair(format, name, value) {
switch (format) {
case "dotenv":
return name + '="' + value + '"' + os.EOL;
case "shell":
return "export " + name + '="' + value + '"' + os.EOL;
default:
return name + '="' + value + '"' + os.EOL;
}
}
}
export { fetchSecretsAws, hasPathvCalls, pathv, processPathvCalls };
//# sourceMappingURL=index.js.map