import { Graph, topologicalSort } from 'graph-data-structure'; import upath from 'upath'; import { logger } from '../../../logger'; import type { PackageFile } from '../types'; import type { DependencyBetweenFiles, PipCompileArgs } from './types'; export function sortPackageFiles( depsBetweenFiles: DependencyBetweenFiles[], packageFiles: Map<string, PackageFile>, ): PackageFile[] { const result: PackageFile[] = []; const graph = new Graph(); depsBetweenFiles.forEach(({ sourceFile, outputFile }) => { graph.addEdge(sourceFile, outputFile); }); const sorted = topologicalSort(graph); for (const file of sorted) { if (packageFiles.has(file)) { const packageFile = packageFiles.get(file)!; const sortedLockFiles = []; // TODO(not7cd): this needs better test case for (const lockFile of packageFile.lockFiles!) { if (sorted.includes(lockFile)) { sortedLockFiles.push(lockFile); } } packageFile.lockFiles = sortedLockFiles; result.push(packageFile); } } // istanbul ignore if: should never happen if (result.length !== packageFiles.size) { throw new Error('Topological sort failed to include all package files'); } return result; } export function generateMermaidGraph( depsBetweenFiles: DependencyBetweenFiles[], lockFileArgs: Map<string, PipCompileArgs>, ): string { const lockFiles = []; for (const lockFile of lockFileArgs.keys()) { // TODO: add extra args to the lock file ${extraArgs ? '\n' + extraArgs : ''} // const extraArgs = pipCompileArgs.extra // ?.map((v) => '--extra=' + v) // .join('\n'); lockFiles.push(` ${lockFile}[[${lockFile}]]`); } const edges = depsBetweenFiles.map(({ sourceFile, outputFile, type }) => { return ` ${sourceFile} -${type === 'constraint' ? '.' : ''}-> ${outputFile}`; }); return `graph TD\n${lockFiles.join('\n')}\n${edges.join('\n')}`; } export function inferCommandExecDir( outputFilePath: string, outputFileArg: string | undefined, ): string { if (!outputFileArg) { // implicit output file is in the same directory where command was executed return upath.normalize(upath.dirname(outputFilePath)); } if (upath.normalize(outputFileArg).startsWith('..')) { throw new Error( `Cannot infer command execution directory from path ${outputFileArg}`, ); } if (upath.basename(outputFileArg) !== upath.basename(outputFilePath)) { throw new Error( `Output file name mismatch: ${upath.basename(outputFileArg)} vs ${upath.basename(outputFilePath)}`, ); } const outputFileDir = upath.normalize(upath.dirname(outputFileArg)); let commandExecDir = upath.normalize(upath.dirname(outputFilePath)); for (const dir of outputFileDir.split('/').reverse()) { if (commandExecDir.endsWith(dir)) { commandExecDir = upath.join(commandExecDir.slice(0, -dir.length), '.'); // outputFileDir = upath.join(outputFileDir.slice(0, -dir.length), '.'); } else { break; } } commandExecDir = upath.normalizeTrim(commandExecDir); if (commandExecDir !== '.') { logger.debug( { commandExecDir, outputFileArg, outputFilePath, }, `pip-compile: command was not executed in repository root`, ); } return commandExecDir; }