-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
129 lines (114 loc) · 3.5 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const callsites = require('callsites')
const debug = require('debug')('then-recursively-search')
const fs = require('fs')
const path = require('path')
const util = require('util')
// for backward compatibility with Node < 14
const fsAccess = fs.promises
? fs.promises.access
: util.promsify(fs.access)
/**
* Throws an exception if the provided value does not pass validation.
*
* @param {String?} filename The value to check.
*
* @return {undefined}
*/
function validateFileName (filename) {
if (filename === undefined) {
throw new Error('the "filename" parameter is required')
}
if (typeof filename !== 'string') {
throw new TypeError('the "filename" parameter must be a string value')
}
}
/**
* Throws an exception if the provided value does not pass validation.
*
* @param {String?} dirname The value to check.
*
* @return {undefined}
*/
function validateStartingPoint (dirname) {
if (!path.isAbsolute(dirname)) {
throw new Error('if specified, the "startIn" parameter must be an absolute path')
}
}
/**
* Returns a Promise that resolves to a Boolean value indicating whether the
* named file exists in the specified folder.
*
* @param {String} filename The named file (including extension).
*
* @param {String} dirname The absolute path of the containing folder.
*
* @return {Promise} Resolves to a {Boolean}.
*/
async function doesFileExist (filename, dirname) {
// fs.exists() is deprecated; docs recommend using access() instead of stat()
try {
await fsAccess(path.join(dirname, filename))
return true
} catch {
return false
}
}
/**
* Returns the parent of the specified folder.
*
* @param {String} dirname The absolute path of the child folder.
*
* @return {String} The absolute path to the parent.
*/
function getParentFolder (dirname) {
return path.dirname(dirname)
}
/**
* Search for `filename` in `dirname`, recursively moving up the directory tree
* if not found.
*
* @param {String} filename The file to find (including extension).
*
* @param {String} dirname The absolute path of the folder to look in.
*
* @return {Promise} Resolves to a {String} containing the complete
* path to the file.
*/
async function searchForFile (filename, dirname) {
debug('searching for "%s" in "%s"', filename, dirname)
const found = await doesFileExist(filename, dirname)
if (found) {
debug('...found!')
return path.join(dirname, filename)
}
const parentFolder = getParentFolder(dirname)
if (parentFolder === dirname) {
// reached the top of the directory tree
debug('...not found, cannot recurse any further')
throw new Error('file not found')
}
debug('...not found, recursing')
return await searchForFile(filename, parentFolder)
}
/**
* The exported function (entry point for this module).
*
* @param {String} filename The file to find (including extension).
*
* @param {String?} startIn (Optional) The absolute path of the folder to
* begin searching in. Defaults to the location
* of the caller.
*
* @return {Promise}
*/
async function exported (filename, startIn) {
validateFileName(filename)
if (startIn === undefined) {
const callstack = callsites()
startIn = path.dirname(callstack[1].getFileName())
} else {
validateStartingPoint(startIn)
}
return searchForFile(filename.toLowerCase(), startIn)
}
module.exports = exported