import path from 'path' ; import fs from 'fs' ; import findUp from 'find-up' ; import { promisify } from 'util' ; import ignore from 'ignore' ; import requireResolve from './requireResolve' ; import { createDebug , warn } from './log' ; import { JS_EXTENSIONS_DOTTED } from './options' ; import type { PathMap } from './options' ; import type { Context } from './transform' ; const { hasOwnProperty } = Object . prototype ; const debug = createDebug ( __filename ) ; const readdir = promisify ( fs . readdir ) ; const stat = promisify ( fs . stat ) ; export const readFile = promisify ( fs . readFile ) ; export const writeFile = promisify ( fs . writeFile ) ; function notEmpty < TValue > ( value ) { return value != null ; } /**
 * Makes sure a path always starts with `/` or `./`.
 * Ex: a.js -> ./a.js
 */ function normalizePath ( _path ) { if ( _path . startsWith ( '.' ) || path . isAbsolute ( _path ) ) { return _path ; } return `./${ _path }` ; } /**
 * When changing the import declarations on existing modules, the updated path
 * should have a similar style to the current path.
 *
 * Ex:
 * -> import x from '../a';
 * <- import x from '../b'; // and not '../b.js'
 *
 * -> import x from '../a/index';
 * <- import x from '../b/index'; // and not '../b'
 */ export function matchPathStyle ( _path , referencePath ) { const referenceExt = path . extname ( referencePath ) ; if ( referenceExt ) { return _path ; } const referenceBasename = path . basename ( referencePath , referenceExt ) ; const pathExt = path . extname ( _path ) ; const pathBasename = path . basename ( _path , pathExt ) ; const pathDirname = path . dirname ( _path ) ; if ( pathBasename === 'index' && referenceBasename !== 'index' ) { return pathDirname ; } return path . join ( pathDirname , pathBasename ) ; } /**
 * Returns the absolute path that represents the path from the source file if
 * the current `file.path` was importing the source.
 * With this value we can check if the relative importSourcePath matches the
 * provided `sourcePath`.
 */ function getAbsoluteImportSourcePath ( context , importSourcePath ) { const { file } = context ; const absolutePath = path . resolve ( path . dirname ( file . path ) , importSourcePath ) ; if ( pathHasJSExtension ( file . path ) ) { return requireResolve ( context , absolutePath ) ; } return absolutePath ; } /**
 * Returns a relative path from `sourcePath` to `targetPath`, using `referencePath` as a "style" or "format" reference.
 * Ex:
 * sourcePath: './folder/a.js'
 * targetPath: './b.js'
 * referencePath: './folder/a'
 * -> '../b'
 */ function generateRelativeNormalizedPath ( sourcePath , targetPath , referencePath ) { return normalizePath ( matchPathStyle ( path . relative ( path . dirname ( sourcePath ) , targetPath ) , referencePath ) ) ; } function generateSourcePathForExternalModule ( sourcePath , targetPath , importSourcePath ) { const targetImportSourcePath = generateRelativeNormalizedPath ( sourcePath , targetPath , importSourcePath ) ; debug ( `Updating ${ sourcePath }: ${ importSourcePath } -> ${ targetImportSourcePath }` ) ; return targetImportSourcePath ; } function generateSourcePathForMovedModule ( sourcePath , targetPath , importSourcePath , absoluteImportPath ) { const targetImportSourcePath = generateRelativeNormalizedPath ( targetPath , absoluteImportPath , importSourcePath ) ; debug ( `Updating ${ sourcePath }: ${ importSourcePath } -> ${ targetImportSourcePath }` ) ; return targetImportSourcePath ; } export function updateSourcePath ( context , importSourcePath ) { const { file } = context ; if ( path . isAbsolute ( importSourcePath ) ) { debug ( `Ignoring absolute path "${ importSourcePath }" from "${ file . path }".` ) ; return importSourcePath ; } if ( ! importSourcePath . startsWith ( '.' ) ) { return importSourcePath ; } const { options } = context ; const absoluteImportSourcePath = getAbsoluteImportSourcePath ( context , importSourcePath ) ; if ( hasOwnProperty . call ( options . expandedPaths , file . path ) ) { const currentFileTargetPath = options . expandedPaths [ file . path ] ; const absoluteImportPath = options . expandedPaths [ absoluteImportSourcePath ] || absoluteImportSourcePath ; return generateSourcePathForMovedModule ( file . path , currentFileTargetPath , importSourcePath , absoluteImportPath ) ; } if ( hasOwnProperty . call ( options . expandedPaths , absoluteImportSourcePath ) ) { const currentFileTargetPath = options . expandedPaths [ file . path ] || file . path ; const targetPath = options . expandedPaths [ absoluteImportSourcePath ] ; return generateSourcePathForExternalModule ( currentFileTargetPath , targetPath , importSourcePath ) ; } return importSourcePath ; } export async function findProjectPath ( ) { const projectPackageJson = await findUp ( 'package.json' ) ; if ( projectPackageJson == null ) { const cwd = process . cwd ( ) ; warn ( `Could not find a "package.json" file on any parent directory, using "${ cwd }" as a fallback.` ) ; return cwd ; } return path . dirname ( projectPackageJson ) ; } function splitByFilter ( array , filterFn ) { const truthyValues = [ ] ; const falsyValues = [ ] ; array . forEach ( ( value ) => { const arr = filterFn ( value ) ? truthyValues : falsyValues ; arr . push ( value ) ; } ) ; return [ truthyValues , falsyValues ] ; } function pathHasExtension ( _path , extensions ) { return extensions . has ( path . extname ( _path ) ) ; } function pathHasJSExtension ( _path ) { return pathHasExtension ( _path , JS_EXTENSIONS_DOTTED ) ; } const IGNORED_FOLDERS = new Set ( [ 'node_modules' , '.git' , '.hg' ] ) ; type FindAllOptions = { ignorePattern : Array < string > ; } ; export async function findAllJSPaths ( rootPath ) { return await findAllPaths ( rootPath , JS_EXTENSIONS_DOTTED ) ; } function applyIgnoreFilter ( paths , ignorePattern ) { if ( ! ignorePattern . length ) { return paths ; } const cwd = process . cwd ( ) ; const ig = ignore ( ) . add ( ignorePattern ) ; return paths . filter ( ( absPath ) => ! ig . ignores ( path . relative ( cwd , absPath ) ) ) ; } export async function findAllPathsCategorized ( rootPath , options ) { const allPaths = await findAllPaths ( rootPath ) ; const filteredPaths = applyIgnoreFilter ( allPaths , options . ignorePattern ) ; const [ js , others ] = splitByFilter ( filteredPaths , pathHasJSExtension ) ; return { js , others } ; } async function findAllPaths ( rootPath , supportedExtensions ? ) { const subFiles = await readdir ( rootPath ) ; const files = await Promise . all ( subFiles . map ( async ( subFile ) => { const res = path . join ( rootPath , subFile ) ; if ( ( await stat ( res ) ) . isDirectory ( ) ) { if ( IGNORED_FOLDERS . has ( subFile ) ) { return null ; } return findAllPaths ( res , supportedExtensions ) ; } else if ( supportedExtensions && ! pathHasExtension ( subFile , supportedExtensions ) ) { return null ; } return res ; } ) ) ; return files . filter ( notEmpty ) . reduce < Array < string > > ( ( a , f ) => a . concat ( f ) , [ ] ) ; } /**
 * Expands directory sourcePaths into its children files with supported
 * file extensions.
 * Ex:
 * <-
 * { '/foo/bar': '/foo/bar/baz' }
 * ->
 * {
 *   '/foo/bar/a.js': '/foo/bar/baz/a.js',
 *   '/foo/bar/b/c.js': '/foo/bar/baz/b/c.js'
 * }
 */ export async function expandDirectoryPaths ( pathMap ) { return await Object . keys ( pathMap ) . map ( ( sourcePath ) => ( { sourcePath , stat : stat ( sourcePath ) } ) ) . reduce < Promise < { [ k ] : string ; } > > ( async ( _acc , { sourcePath , stat } ) => { const acc = await _acc ; if ( ( await stat ) . isDirectory ( ) ) { const allDescendants = await findAllPaths ( sourcePath ) ; const targetPath = pathMap [ sourcePath ] ; allDescendants . forEach ( ( descendant ) => { acc [ descendant ] = path . join ( targetPath , path . relative ( sourcePath , descendant ) ) ; } ) ; } else { acc [ sourcePath ] = pathMap [ sourcePath ] ; } return acc ; } , Promise . resolve ( { } ) ) ; }