UNPKG

@harboor/core

Version:
231 lines (225 loc) 8.41 kB
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