import path from 'path' ; import { RedspotError } from '../internal/core/errors' ; import { ERRORS } from '../internal/core/errors-list' ; const NODE_MODULES = 'node_modules' ; /**
 * This function validates the source name's format.
 *
 * It throws if the format is invalid.
 * If it doesn't throw all you know is that the format is valid.
 */ export function validateSourceNameFormat ( sourceName ) { if ( isAbsolutePathSourceName ( sourceName ) ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . INVALID_SOURCE_NAME_ABSOLUTE_PATH , { name : sourceName } ) ; } if ( isExplicitRelativePath ( sourceName ) ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . INVALID_SOURCE_NAME_RELATIVE_PATH , { name : sourceName } ) ; } if ( replaceBackslashes ( sourceName ) !== sourceName ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . INVALID_SOURCE_NAME_BACKSLASHES , { name : sourceName } ) ; } if ( normalizeSourceName ( sourceName ) !== sourceName ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . INVALID_SOURCE_NOT_NORMALIZED , { name : sourceName } ) ; } } /**
 * This function returns true if the sourceName is, potentially, from a local
 * file. It doesn't validate that the file actually exists.
 *
 * The source name must be in a valid format.
 */ export async function isLocalSourceName ( projectRoot , sourceName ) { if ( sourceName . includes ( NODE_MODULES ) ) { return false ; } const slashIndex = sourceName . indexOf ( '/' ) ; const firstDirOrFileName = slashIndex !== - 1 ? sourceName . substring ( 0 , slashIndex ) : sourceName ; try { await getPathTrueCase ( projectRoot , firstDirOrFileName ) ; } catch ( error ) { if ( RedspotError . isRedspotErrorType ( error , ERRORS . SOURCE_NAMES . FILE_NOT_FOUND ) ) { return false ; } throw error ; } return true ; } /**
 * Validates that a source name exists, starting from `fromDir`, and has the
 * right casing.
 *
 * The source name must be in a valid format.
 */ export async function validateSourceNameExistenceAndCasing ( fromDir , sourceName ) { const trueCaseSourceName = await getPathTrueCase ( fromDir , sourceName ) ; if ( trueCaseSourceName !== sourceName ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . WRONG_CASING , { incorrect : sourceName , correct : trueCaseSourceName } ) ; } } /**
 * Returns the source name of an existing local file's absolute path.
 *
 * Throws is the file doesn't exist, it's not inside the project, or belongs
 * to a library.
 */ export async function localPathToSourceName ( projectRoot , localFileAbsolutePath ) { const relativePath = path . relative ( projectRoot , localFileAbsolutePath ) ; const normalized = normalizeSourceName ( relativePath ) ; if ( normalized . startsWith ( '..' ) ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . EXTERNAL_AS_LOCAL , { path : localFileAbsolutePath } ) ; } if ( normalized . includes ( NODE_MODULES ) ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . NODE_MODULES_AS_LOCAL , { path : localFileAbsolutePath } ) ; } return getPathTrueCase ( projectRoot , relativePath ) ; } /**
 * This function takes a valid local source name and returns its path. The
 * source name doesn't need to point to an existing file.
 */ export function localSourceNameToPath ( projectRoot , sourceName ) { return path . join ( projectRoot , sourceName ) ; } /**
 * Normalizes the source name, for example, by replacing `a/./b` with `a/b`.
 *
 * The sourceName param doesn't have to be a valid source name. It can,
 * for example, be denormalized.
 */ export function normalizeSourceName ( sourceName ) { return replaceBackslashes ( path . normalize ( sourceName ) ) ; } /**
 * This function returns true if the sourceName is a unix absolute path or a
 * platform-dependent one.
 *
 * This function is used instead of just `path.isAbsolute` to ensure that
 * source names never start with `/`, even on Windows.
 */ export function isAbsolutePathSourceName ( sourceName ) { return path . isAbsolute ( sourceName ) || sourceName . startsWith ( '/' ) ; } /**
 * This function returns true if the sourceName is a unix path that is based on
 * the current directory `./`.
 */ function isExplicitRelativePath ( sourceName ) { const [ base ] = sourceName . split ( '/' , 1 ) ; return base === '.' || base === '..' ; } /**
 * This function replaces backslashes (\) with slashes (/).
 *
 * Note that a source name must not contain backslashes.
 */ export function replaceBackslashes ( str ) { const slash = require ( 'slash' ) ; return slash ( str ) ; } /**
 * Returns the true casing of `p` as a relative path from `fromDir`. Throws if
 * `p` doesn't exist. `p` MUST be in source name format.
 */ async function getPathTrueCase ( fromDir , p ) { const { trueCasePath } = await import ( 'true-case-path' ) ; try { const tcp = await trueCasePath ( p , fromDir ) ; return normalizeSourceName ( path . relative ( fromDir , tcp ) ) ; } catch ( error ) { if ( typeof error . message === 'string' && error . message . includes ( 'no matching file exists' ) ) { throw new RedspotError ( ERRORS . SOURCE_NAMES . FILE_NOT_FOUND , { name : p } , error ) ; } throw error ; } }