-
Notifications
You must be signed in to change notification settings - Fork 87
Expand file tree
/
Copy pathsearch.js
More file actions
138 lines (127 loc) · 3.62 KB
/
search.js
File metadata and controls
138 lines (127 loc) · 3.62 KB
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
130
131
132
133
134
135
136
137
138
/**
* Provides the behavior for `node-modules.js` to find modules and
* packages according to the Node.js `node_modules` convention.
*
* @module
*/
/**
* @import {
* ReadFn,
* ReadPowers,
* MaybeReadPowers,
* SearchOptions,
* SearchResult,
* SearchDescriptorResult,
* MaybeReadDescriptorFn,
* SearchDescriptorOptions,
* } from './types.js'
*/
import { relativize } from './node-module-specifier.js';
import { relative } from './url.js';
import { unpackReadPowers } from './powers.js';
// q, as in quote, for enquoting strings in error messages.
const q = JSON.stringify;
/**
* Default logger that does nothing
*/
const noop = () => {};
const decoder = new TextDecoder();
/**
* @param {string} rel - a relative URL
* @param {string} abs - a fully qualified URL
* @returns {string}
*/
const resolveLocation = (rel, abs) => new URL(rel, abs).toString();
/**
* Searches for the first ancestor directory of given location that contains a
* package.json.
* Probes by calling maybeReadDescriptor.
* Returns the result of maybeReadDescriptor as data whenever a value is returned.
*
* @template T
* @param {string} location
* @param {MaybeReadDescriptorFn<T>} maybeReadDescriptor
* @param {SearchDescriptorOptions} options
* @returns {Promise<SearchDescriptorResult<T>>}
*/
export const searchDescriptor = async (
location,
maybeReadDescriptor,
{ log: _log = noop } = {},
) => {
await null;
let directory = resolveLocation('./', location);
for (;;) {
const packageDescriptorLocation = resolveLocation(
'package.json',
directory,
);
// eslint-disable-next-line no-await-in-loop
const data = await maybeReadDescriptor(packageDescriptorLocation);
if (data !== undefined) {
return {
data,
directory,
location,
packageDescriptorLocation,
};
}
const parentDirectory = resolveLocation('../', directory);
if (parentDirectory === directory) {
throw Error(`Cannot find package.json along path to ${q(location)}`);
}
directory = parentDirectory;
}
};
/**
* @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
* @param {string} packageDescriptorLocation
* @returns {Promise<string | undefined>}
*/
const maybeReadDescriptorDefault = async (
readPowers,
packageDescriptorLocation,
// eslint-disable-next-line consistent-return
) => {
const { maybeRead } = unpackReadPowers(readPowers);
const packageDescriptorBytes = await maybeRead(packageDescriptorLocation);
if (packageDescriptorBytes !== undefined) {
const packageDescriptorText = decoder.decode(packageDescriptorBytes);
return packageDescriptorText;
}
};
/**
* Searches for the first ancestor directory of a module file that contains a
* package.json.
* Probes by attempting to read the file, not stat.
* To avoid duplicate work later, returns the text of the package.json for
* inevitable later use.
*
* @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
* @param {string} moduleLocation
* @param {SearchOptions} [options]
* @returns {Promise<SearchResult>}
*/
export const search = async (
readPowers,
moduleLocation,
{ log = noop } = {},
) => {
const { data, directory, location, packageDescriptorLocation } =
await searchDescriptor(
moduleLocation,
loc => maybeReadDescriptorDefault(readPowers, loc),
{ log },
);
if (!data) {
throw Error(
`Cannot find package.json along path to module ${q(moduleLocation)}`,
);
}
return {
packageLocation: directory,
packageDescriptorText: data,
packageDescriptorLocation,
moduleSpecifier: relativize(relative(directory, location)),
};
};