@remix-run/assets 0.4.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -72
- package/dist/assets.d.ts +4 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +35 -4
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +332 -11
- package/dist/lib/asset-server.d.ts +138 -11
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +281 -30
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +9 -8
- package/dist/lib/files/config.js +1 -1
- package/dist/lib/hmr.d.ts +37 -0
- package/dist/lib/hmr.d.ts.map +1 -0
- package/dist/lib/hmr.js +357 -0
- package/dist/lib/injected-packages.d.ts +3 -2
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +43 -17
- package/dist/lib/inspection.d.ts +39 -0
- package/dist/lib/inspection.d.ts.map +1 -0
- package/dist/lib/inspection.js +160 -0
- package/dist/lib/loaders.d.ts +57 -0
- package/dist/lib/loaders.d.ts.map +1 -0
- package/dist/lib/loaders.js +1 -0
- package/dist/lib/module-store.d.ts +24 -0
- package/dist/lib/module-store.d.ts.map +1 -1
- package/dist/lib/module-store.js +136 -11
- package/dist/lib/routes.d.ts +11 -3
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +124 -80
- package/dist/lib/scripts/compiler.d.ts +24 -1
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +183 -29
- package/dist/lib/scripts/conditions.d.ts +2 -0
- package/dist/lib/scripts/conditions.d.ts.map +1 -0
- package/dist/lib/scripts/conditions.js +1 -0
- package/dist/lib/scripts/emit.d.ts +3 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +24 -5
- package/dist/lib/scripts/resolve.d.ts +4 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +75 -10
- package/dist/lib/scripts/transform.d.ts +9 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +222 -18
- package/dist/lib/source-maps.js +1 -1
- package/dist/lib/styles/compiler.d.ts +14 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +78 -14
- package/dist/lib/styles/emit.js +4 -4
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +16 -15
- package/dist/lib/styles/transform.js +5 -5
- package/dist/lib/target.d.ts +1 -1
- package/dist/lib/target.d.ts.map +1 -1
- package/dist/lib/watch.js +1 -1
- package/dist/types/hmr.d.ts +36 -0
- package/package.json +16 -11
- package/src/assets.ts +4 -1
- package/src/lib/access.ts +445 -11
- package/src/lib/asset-server.ts +466 -27
- package/src/lib/compilation-error.ts +4 -3
- package/src/lib/files/compiler.ts +9 -5
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +52 -16
- package/src/lib/inspection.ts +229 -0
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +248 -24
- package/src/lib/scripts/conditions.ts +1 -0
- package/src/lib/scripts/emit.ts +50 -5
- package/src/lib/scripts/resolve.ts +129 -7
- package/src/lib/scripts/transform.ts +290 -19
- package/src/lib/styles/compiler.ts +108 -8
- package/src/lib/styles/emit.ts +1 -1
- package/src/lib/styles/resolve.ts +19 -15
- package/src/lib/styles/transform.ts +2 -2
- package/src/types/hmr.d.ts +36 -0
package/src/lib/access.ts
CHANGED
|
@@ -1,26 +1,460 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
1
3
|
import { createFileMatcher } from './file-matcher.ts'
|
|
2
4
|
import { isInjectedPackageFilePath } from './injected-packages.ts'
|
|
5
|
+
import { normalizeFilePath } from './paths.ts'
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
/** Access-policy result for an inspected asset file. */
|
|
8
|
+
export interface AssetAccessDetails {
|
|
9
|
+
/** Whether the asset server may serve the file. */
|
|
10
|
+
allowed: boolean
|
|
11
|
+
/** The first configured rule that allowed the file, when one matched. */
|
|
12
|
+
allowedBy?: AssetAccessRule
|
|
13
|
+
/** The first matching `denyFiles` pattern, when access was denied. */
|
|
14
|
+
deniedBy?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rule that allows an inspected asset file to be served. */
|
|
18
|
+
export type AssetAccessRule =
|
|
19
|
+
/** A matching `allowFiles` entry. */
|
|
20
|
+
| { kind: 'file'; value: string }
|
|
21
|
+
/** A runtime file provided internally by the asset server. */
|
|
22
|
+
| { kind: 'injected'; value: string }
|
|
23
|
+
/** A matching `allowPackages` entry. */
|
|
24
|
+
| { kind: 'package'; value: string }
|
|
25
|
+
|
|
26
|
+
export type AccessPolicy = {
|
|
27
|
+
getAllowedPackageRoots(): readonly string[]
|
|
28
|
+
getPackageWatchDirectories(): readonly string[]
|
|
29
|
+
handleFileEvent(filePath: string): void
|
|
30
|
+
inspect(filePath: string): AssetAccessDetails
|
|
5
31
|
isAllowed(filePath: string): boolean
|
|
6
32
|
}
|
|
7
33
|
|
|
34
|
+
const packageStateFileNames = new Set([
|
|
35
|
+
'bun.lock',
|
|
36
|
+
'bun.lockb',
|
|
37
|
+
'npm-shrinkwrap.json',
|
|
38
|
+
'package-lock.json',
|
|
39
|
+
'pnpm-lock.yaml',
|
|
40
|
+
'pnpm-workspace.yaml',
|
|
41
|
+
'yarn.lock',
|
|
42
|
+
])
|
|
43
|
+
const packageManagerRootFileNames = packageStateFileNames
|
|
44
|
+
const packageNamePartPattern = /^[A-Za-z0-9._~-]+$/
|
|
45
|
+
|
|
8
46
|
export function createAccessPolicy(options: {
|
|
9
|
-
|
|
10
|
-
|
|
47
|
+
allowFiles: readonly string[]
|
|
48
|
+
allowPackages?: readonly string[]
|
|
49
|
+
denyFiles?: readonly string[]
|
|
50
|
+
packageSearchRoots?: readonly string[]
|
|
11
51
|
rootDir: string
|
|
12
52
|
}): AccessPolicy {
|
|
13
|
-
let allowMatchers = options.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
53
|
+
let allowMatchers = options.allowFiles.map((pattern) => ({
|
|
54
|
+
matcher: createFileMatcher(pattern, options.rootDir),
|
|
55
|
+
pattern,
|
|
56
|
+
}))
|
|
57
|
+
let allowPackageNames = normalizePackageNames(options.allowPackages, 'allowPackages')
|
|
58
|
+
let denyMatchers = (options.denyFiles ?? []).map((pattern) => ({
|
|
59
|
+
matcher: createFileMatcher(pattern, options.rootDir),
|
|
60
|
+
pattern,
|
|
61
|
+
}))
|
|
62
|
+
let packageSearchRoots = [options.rootDir, ...(options.packageSearchRoots ?? [])]
|
|
63
|
+
let packageRootPaths = createPackageRootPaths({
|
|
64
|
+
allowPackageNames,
|
|
65
|
+
searchRoots: packageSearchRoots,
|
|
66
|
+
})
|
|
67
|
+
let allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths)
|
|
68
|
+
let packageStateDirectories =
|
|
69
|
+
allowPackageNames.size === 0 ? [] : getPackageStateDirectories(packageSearchRoots)
|
|
70
|
+
let packageRootsDirty = false
|
|
71
|
+
|
|
72
|
+
function refreshPackageRootPathTries(): void {
|
|
73
|
+
if (!packageRootsDirty) return
|
|
74
|
+
|
|
75
|
+
packageRootPaths = createPackageRootPaths({
|
|
76
|
+
allowPackageNames,
|
|
77
|
+
searchRoots: packageSearchRoots,
|
|
78
|
+
})
|
|
79
|
+
allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths)
|
|
80
|
+
packageRootsDirty = false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getAllowedPackageName(filePath: string): string | undefined {
|
|
84
|
+
if (allowPackageNames.size === 0) return undefined
|
|
85
|
+
refreshPackageRootPathTries()
|
|
86
|
+
|
|
87
|
+
return getPackageNameFromRootPathTrie(filePath, allowPackageRootPathTrie)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function inspect(filePath: string): AssetAccessDetails {
|
|
91
|
+
if (isInjectedPackageFilePath(filePath)) {
|
|
92
|
+
return { allowed: true, allowedBy: { kind: 'injected', value: '@remix-run/assets' } }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let allowedBy: AssetAccessRule | undefined
|
|
96
|
+
let allowMatch = allowMatchers.find(({ matcher }) => matcher(filePath))
|
|
97
|
+
if (allowMatch) {
|
|
98
|
+
allowedBy = { kind: 'file', value: allowMatch.pattern }
|
|
99
|
+
} else {
|
|
100
|
+
let packageName = getAllowedPackageName(filePath)
|
|
101
|
+
if (packageName !== undefined) {
|
|
102
|
+
allowedBy = { kind: 'package', value: packageName }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!allowedBy) return { allowed: false }
|
|
107
|
+
|
|
108
|
+
let denyMatch = denyMatchers.find(({ matcher }) => matcher(filePath))
|
|
109
|
+
if (denyMatch) {
|
|
110
|
+
return { allowed: false, allowedBy, deniedBy: denyMatch.pattern }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { allowed: true, allowedBy }
|
|
114
|
+
}
|
|
17
115
|
|
|
18
116
|
return {
|
|
117
|
+
getAllowedPackageRoots() {
|
|
118
|
+
refreshPackageRootPathTries()
|
|
119
|
+
return [...packageRootPaths.keys()]
|
|
120
|
+
},
|
|
121
|
+
getPackageWatchDirectories() {
|
|
122
|
+
if (allowPackageNames.size === 0) return []
|
|
123
|
+
return packageStateDirectories
|
|
124
|
+
},
|
|
125
|
+
handleFileEvent(filePath) {
|
|
126
|
+
if (allowPackageNames.size === 0) return
|
|
127
|
+
if (!isPackageStateFileEvent(filePath, packageStateDirectories)) return
|
|
128
|
+
|
|
129
|
+
packageRootsDirty = true
|
|
130
|
+
},
|
|
131
|
+
inspect,
|
|
19
132
|
isAllowed(filePath) {
|
|
20
|
-
|
|
21
|
-
if (!allowMatchers.some((matcher) => matcher(filePath))) return false
|
|
22
|
-
if (denyMatchers.length > 0 && denyMatchers.some((matcher) => matcher(filePath))) return false
|
|
23
|
-
return true
|
|
133
|
+
return inspect(filePath).allowed
|
|
24
134
|
},
|
|
25
135
|
}
|
|
26
136
|
}
|
|
137
|
+
|
|
138
|
+
function normalizePackageNames(
|
|
139
|
+
packageOption: readonly string[] | undefined,
|
|
140
|
+
optionName: 'allowPackages',
|
|
141
|
+
): Set<string> {
|
|
142
|
+
let packageNames = new Set<string>()
|
|
143
|
+
|
|
144
|
+
for (let packageName of packageOption ?? []) {
|
|
145
|
+
if (typeof packageName !== 'string') {
|
|
146
|
+
throw new TypeError(`${optionName} values must be strings`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let normalizedPackageName = packageName.trim()
|
|
150
|
+
if (!isValidPackageName(normalizedPackageName)) {
|
|
151
|
+
throw new TypeError(`${optionName} values must be package names. Received "${packageName}".`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
packageNames.add(normalizedPackageName)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return packageNames
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function validatePackageName(packageName: string, message: string): void {
|
|
161
|
+
if (!isValidPackageName(packageName)) {
|
|
162
|
+
throw new TypeError(message)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type PackageJson = {
|
|
167
|
+
dependencies?: Record<string, string>
|
|
168
|
+
optionalDependencies?: Record<string, string>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type PackageRootPathTrie = {
|
|
172
|
+
children: Map<string, PackageRootPathTrie>
|
|
173
|
+
packageName?: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type PackageRootQueueItem = {
|
|
177
|
+
packageJsonPath: string
|
|
178
|
+
packageName: string
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function createPackageRootPaths(options: {
|
|
182
|
+
allowPackageNames: ReadonlySet<string>
|
|
183
|
+
searchRoots: readonly string[]
|
|
184
|
+
}): Map<string, string> {
|
|
185
|
+
let allowPackageRootPaths = new Map<string, string>()
|
|
186
|
+
let allowQueue: PackageRootQueueItem[] = []
|
|
187
|
+
let seenAllowedPackageRoots = new Set<string>()
|
|
188
|
+
let searchRoots = normalizePackageSearchRoots(options.searchRoots)
|
|
189
|
+
|
|
190
|
+
for (let packageName of options.allowPackageNames) {
|
|
191
|
+
let foundPackage = false
|
|
192
|
+
|
|
193
|
+
for (let searchRoot of searchRoots) {
|
|
194
|
+
let packageJsonPath = findPackageJsonPath(packageName, searchRoot)
|
|
195
|
+
if (packageJsonPath === null) continue
|
|
196
|
+
|
|
197
|
+
foundPackage = true
|
|
198
|
+
allowQueue.push({ packageJsonPath, packageName })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!foundPackage) {
|
|
202
|
+
throw new TypeError(`Could not resolve allowed package "${packageName}".`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
while (allowQueue.length > 0) {
|
|
207
|
+
let { packageJsonPath, packageName } = allowQueue.shift()!
|
|
208
|
+
let packageRootPath = normalizeFilePath(path.dirname(packageJsonPath))
|
|
209
|
+
if (seenAllowedPackageRoots.has(packageRootPath)) continue
|
|
210
|
+
seenAllowedPackageRoots.add(packageRootPath)
|
|
211
|
+
|
|
212
|
+
let packageJson = readPackageJson(packageJsonPath)
|
|
213
|
+
allowPackageRootPaths.set(packageRootPath, packageName)
|
|
214
|
+
|
|
215
|
+
for (let dependencyName of Object.keys(packageJson.dependencies ?? {})) {
|
|
216
|
+
validatePackageName(
|
|
217
|
+
dependencyName,
|
|
218
|
+
`Dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`,
|
|
219
|
+
)
|
|
220
|
+
let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath)
|
|
221
|
+
if (dependencyPackageJsonPath === null) {
|
|
222
|
+
throw new TypeError(
|
|
223
|
+
`Could not resolve dependency "${dependencyName}" from ${packageJsonPath}.`,
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
allowQueue.push({
|
|
227
|
+
packageJsonPath: dependencyPackageJsonPath,
|
|
228
|
+
packageName: dependencyName,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
for (let dependencyName of Object.keys(packageJson.optionalDependencies ?? {})) {
|
|
232
|
+
validatePackageName(
|
|
233
|
+
dependencyName,
|
|
234
|
+
`Optional dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`,
|
|
235
|
+
)
|
|
236
|
+
let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath)
|
|
237
|
+
if (dependencyPackageJsonPath !== null) {
|
|
238
|
+
allowQueue.push({
|
|
239
|
+
packageJsonPath: dependencyPackageJsonPath,
|
|
240
|
+
packageName: dependencyName,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return allowPackageRootPaths
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function createPackageRootPathTrie(
|
|
250
|
+
packageRootPaths: ReadonlyMap<string, string>,
|
|
251
|
+
): PackageRootPathTrie {
|
|
252
|
+
let rootNode = createPackageRootPathTrieNode()
|
|
253
|
+
|
|
254
|
+
for (let [packageRootPath, packageName] of packageRootPaths) {
|
|
255
|
+
let node = rootNode
|
|
256
|
+
for (let segment of getFilePathSegments(packageRootPath)) {
|
|
257
|
+
let childNode = node.children.get(segment)
|
|
258
|
+
if (!childNode) {
|
|
259
|
+
childNode = createPackageRootPathTrieNode()
|
|
260
|
+
node.children.set(segment, childNode)
|
|
261
|
+
}
|
|
262
|
+
node = childNode
|
|
263
|
+
}
|
|
264
|
+
node.packageName = packageName
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return rootNode
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function createPackageRootPathTrieNode(): PackageRootPathTrie {
|
|
271
|
+
return {
|
|
272
|
+
children: new Map(),
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getPackageNameFromRootPathTrie(
|
|
277
|
+
filePath: string,
|
|
278
|
+
trie: PackageRootPathTrie,
|
|
279
|
+
): string | undefined {
|
|
280
|
+
let node = trie
|
|
281
|
+
if (node.packageName !== undefined) return node.packageName
|
|
282
|
+
|
|
283
|
+
for (let segment of getFilePathSegments(filePath)) {
|
|
284
|
+
let childNode = node.children.get(segment)
|
|
285
|
+
if (!childNode) return undefined
|
|
286
|
+
if (childNode.packageName !== undefined) return childNode.packageName
|
|
287
|
+
node = childNode
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return undefined
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getFilePathSegments(filePath: string): string[] {
|
|
294
|
+
return normalizeFilePath(filePath).split('/')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizePackageSearchRoots(searchRoots: readonly string[]): string[] {
|
|
298
|
+
let normalizedSearchRoots = new Set<string>()
|
|
299
|
+
|
|
300
|
+
for (let searchRoot of searchRoots) {
|
|
301
|
+
let normalizedSearchRoot = normalizeFilePath(searchRoot)
|
|
302
|
+
normalizedSearchRoots.add(normalizedSearchRoot)
|
|
303
|
+
|
|
304
|
+
if (path.basename(normalizedSearchRoot) === 'node_modules') {
|
|
305
|
+
normalizedSearchRoots.add(path.dirname(normalizedSearchRoot))
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return [...normalizedSearchRoots]
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function getPackageStateDirectories(searchRoots: readonly string[]): string[] {
|
|
313
|
+
let packageStateDirectories = new Set<string>()
|
|
314
|
+
|
|
315
|
+
for (let searchRoot of searchRoots) {
|
|
316
|
+
let packageManagerRoot = findPackageManagerRoot(searchRoot)
|
|
317
|
+
if (packageManagerRoot !== null) {
|
|
318
|
+
packageStateDirectories.add(packageManagerRoot)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return [...packageStateDirectories]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isPackageStateFileEvent(
|
|
326
|
+
filePath: string,
|
|
327
|
+
packageStateDirectories: readonly string[],
|
|
328
|
+
): boolean {
|
|
329
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
330
|
+
let fileName = path.basename(normalizedFilePath)
|
|
331
|
+
if (!packageStateFileNames.has(fileName)) return false
|
|
332
|
+
|
|
333
|
+
let directory = path.dirname(normalizedFilePath)
|
|
334
|
+
return packageStateDirectories.some(
|
|
335
|
+
(packageStateDirectory) => directory === packageStateDirectory,
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function findPackageManagerRoot(startDirectory: string): string | null {
|
|
340
|
+
let directory = normalizePackageStateSearchRoot(startDirectory)
|
|
341
|
+
|
|
342
|
+
while (true) {
|
|
343
|
+
if (hasPackageManagerRootFile(directory)) return directory
|
|
344
|
+
|
|
345
|
+
let parentDirectory = path.dirname(directory)
|
|
346
|
+
if (parentDirectory === directory) return null
|
|
347
|
+
directory = parentDirectory
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function normalizePackageStateSearchRoot(searchRoot: string): string {
|
|
352
|
+
let normalizedSearchRoot = normalizeFilePath(searchRoot)
|
|
353
|
+
return path.basename(normalizedSearchRoot) === 'node_modules'
|
|
354
|
+
? path.dirname(normalizedSearchRoot)
|
|
355
|
+
: normalizedSearchRoot
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function hasPackageManagerRootFile(directory: string): boolean {
|
|
359
|
+
for (let fileName of packageManagerRootFileNames) {
|
|
360
|
+
try {
|
|
361
|
+
let stat = fs.statSync(path.join(directory, fileName))
|
|
362
|
+
if (stat.isFile()) return true
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (isPathNotFoundError(error)) continue
|
|
365
|
+
throw error
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return false
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function findPackageJsonPath(packageName: string, startDirectory: string): string | null {
|
|
373
|
+
let directory = normalizeFilePath(startDirectory)
|
|
374
|
+
|
|
375
|
+
while (true) {
|
|
376
|
+
let packageJsonPath = resolvePackageJsonPath(directory, packageName)
|
|
377
|
+
if (packageJsonPath !== null) return packageJsonPath
|
|
378
|
+
|
|
379
|
+
let parentDirectory = path.dirname(directory)
|
|
380
|
+
if (parentDirectory === directory) return null
|
|
381
|
+
directory = parentDirectory
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resolvePackageJsonPath(directory: string, packageName: string): string | null {
|
|
386
|
+
let packagePath =
|
|
387
|
+
path.basename(directory) === 'node_modules'
|
|
388
|
+
? path.join(directory, packageName, 'package.json')
|
|
389
|
+
: path.join(directory, 'node_modules', packageName, 'package.json')
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
return normalizeFilePath(fs.realpathSync(packagePath))
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (isPathNotFoundError(error)) return null
|
|
395
|
+
throw error
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function readPackageJson(packageJsonPath: string): PackageJson {
|
|
400
|
+
let packageJson: unknown
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as unknown
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (isPathNotFoundError(error)) return {}
|
|
406
|
+
throw error
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (packageJson === null || typeof packageJson !== 'object') {
|
|
410
|
+
return {}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
dependencies: readDependencyMap(packageJson, 'dependencies'),
|
|
415
|
+
optionalDependencies: readDependencyMap(packageJson, 'optionalDependencies'),
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function readDependencyMap(packageJson: object, key: string): Record<string, string> | undefined {
|
|
420
|
+
if (!(key in packageJson)) return undefined
|
|
421
|
+
|
|
422
|
+
let value = packageJson[key as keyof typeof packageJson]
|
|
423
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
424
|
+
return undefined
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let dependencies: Record<string, string> = {}
|
|
428
|
+
for (let [dependencyName, dependencyVersion] of Object.entries(value)) {
|
|
429
|
+
if (typeof dependencyVersion === 'string') {
|
|
430
|
+
dependencies[dependencyName] = dependencyVersion
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return dependencies
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isValidPackageName(packageName: string): boolean {
|
|
438
|
+
if (packageName.length === 0) return false
|
|
439
|
+
|
|
440
|
+
let packageNameParts = packageName.startsWith('@')
|
|
441
|
+
? packageName.slice(1).split('/')
|
|
442
|
+
: packageName.split('/')
|
|
443
|
+
|
|
444
|
+
if (packageNameParts.length !== (packageName.startsWith('@') ? 2 : 1)) return false
|
|
445
|
+
|
|
446
|
+
return packageNameParts.every(
|
|
447
|
+
(part) => part.length > 0 && part !== '.' && part !== '..' && packageNamePartPattern.test(part),
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function isPathNotFoundError(
|
|
452
|
+
error: unknown,
|
|
453
|
+
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
454
|
+
return (
|
|
455
|
+
error instanceof Error &&
|
|
456
|
+
'code' in error &&
|
|
457
|
+
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
458
|
+
(error as NodeJS.ErrnoException).code === 'ENOTDIR')
|
|
459
|
+
)
|
|
460
|
+
}
|