@remix-run/assets 0.4.3 → 0.5.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 +322 -56
- package/dist/assets.d.ts +2 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +6 -2
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +300 -5
- package/dist/lib/asset-server.d.ts +118 -5
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +260 -24
- 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 +7 -6
- 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.map +1 -1
- package/dist/lib/injected-packages.js +36 -13
- 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.map +1 -1
- package/dist/lib/routes.js +13 -13
- 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 +70 -5
- 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 +8 -7
- package/dist/lib/styles/transform.js +3 -3
- 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 +17 -11
- package/src/assets.ts +2 -1
- package/src/lib/access.ts +386 -5
- package/src/lib/asset-server.ts +429 -18
- package/src/lib/compilation-error.ts +1 -0
- package/src/lib/files/compiler.ts +7 -3
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +41 -11
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/routes.ts +24 -14
- 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 +124 -2
- 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 +11 -7
- package/src/types/hmr.d.ts +36 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface ImportMetaHot {
|
|
2
|
+
/** Mutable state preserved for this module across accepted updates and passed to dispose handlers. */
|
|
3
|
+
readonly data: Record<string, unknown>
|
|
4
|
+
/** Accepts updates to this module, optionally receiving its newly evaluated namespace. */
|
|
5
|
+
accept(callback?: (module: HotModule) => HotCallbackResult): void
|
|
6
|
+
/** Accepts updates from one dependency, optionally receiving its newly evaluated namespace. */
|
|
7
|
+
accept(dep: string, callback?: (module: HotModule) => HotCallbackResult): void
|
|
8
|
+
/**
|
|
9
|
+
* Accepts updates from multiple dependencies. The callback array preserves `deps` order and
|
|
10
|
+
* contains the updated namespace only at the position of the dependency that changed.
|
|
11
|
+
*/
|
|
12
|
+
accept(
|
|
13
|
+
deps: readonly string[],
|
|
14
|
+
callback?: (modules: Array<HotModule | undefined>) => HotCallbackResult,
|
|
15
|
+
): void
|
|
16
|
+
/** Registers cleanup that runs before this module is re-evaluated or the runtime is disposed. */
|
|
17
|
+
dispose(callback: (data: Record<string, unknown>) => HotCallbackResult): void
|
|
18
|
+
/** Declines the current update and reloads the page if no importing boundary accepts it. */
|
|
19
|
+
invalidate(message?: string): void
|
|
20
|
+
/** Registers a listener for custom events from the browser HMR channel. */
|
|
21
|
+
on(event: string, callback: (data: unknown) => void | Promise<void>): void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type HotModule = Readonly<Record<string, unknown>> & {
|
|
25
|
+
readonly [Symbol.toStringTag]: 'Module'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type HotCallbackResult = void | Promise<void>
|
|
29
|
+
|
|
30
|
+
declare global {
|
|
31
|
+
interface ImportMeta {
|
|
32
|
+
readonly hot?: ImportMetaHot
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Fetch-based server for compiling browser JS/TS and CSS assets on demand",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"types": "./dist/assets.d.ts",
|
|
24
24
|
"default": "./dist/assets.js"
|
|
25
25
|
},
|
|
26
|
+
"./types/hmr": {
|
|
27
|
+
"types": "./dist/types/hmr.d.ts"
|
|
28
|
+
},
|
|
26
29
|
"./package.json": "./package.json"
|
|
27
30
|
},
|
|
28
31
|
"dependencies": {
|
|
@@ -38,17 +41,20 @@
|
|
|
38
41
|
"oxc-transform": "^0.121.0",
|
|
39
42
|
"picomatch": "^4.0.4",
|
|
40
43
|
"source-map-js": "^1.2.1",
|
|
41
|
-
"@remix-run/file-storage": "0.13.
|
|
42
|
-
"@remix-run/headers": "0.21.1",
|
|
43
|
-
"@remix-run/mime": "0.4.
|
|
44
|
-
"@remix-run/route-pattern": "0.
|
|
44
|
+
"@remix-run/file-storage": "^0.13.7",
|
|
45
|
+
"@remix-run/headers": "^0.21.1",
|
|
46
|
+
"@remix-run/mime": "^0.4.2",
|
|
47
|
+
"@remix-run/route-pattern": "^0.24.0"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/node": "^24.6.0",
|
|
48
51
|
"@types/picomatch": "^4.0.3",
|
|
49
|
-
"
|
|
52
|
+
"typescript": "^7.0.2",
|
|
50
53
|
"@remix-run/assert": "0.3.0",
|
|
51
|
-
"@remix-run/
|
|
54
|
+
"@remix-run/node-hmr": "^0.1.0",
|
|
55
|
+
"@remix-run/node-tsx": "^0.1.1",
|
|
56
|
+
"@remix-run/test": "0.6.0",
|
|
57
|
+
"@remix-run/ui": "0.5.0"
|
|
52
58
|
},
|
|
53
59
|
"keywords": [
|
|
54
60
|
"remix",
|
|
@@ -61,10 +67,10 @@
|
|
|
61
67
|
],
|
|
62
68
|
"scripts": {
|
|
63
69
|
"bench": "pnpm --dir ./bench run bench",
|
|
64
|
-
"build": "
|
|
70
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/copy-package-type-only-exports.ts .",
|
|
65
71
|
"clean": "git clean -fdX",
|
|
66
|
-
"test": "remix
|
|
67
|
-
"test:bun": "bun x --bun remix
|
|
68
|
-
"typecheck": "
|
|
72
|
+
"test": "remix test",
|
|
73
|
+
"test:bun": "bun x --bun remix test --type server",
|
|
74
|
+
"typecheck": "tsc --noEmit"
|
|
69
75
|
}
|
|
70
76
|
}
|
package/src/assets.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createAssetServer } from './lib/asset-server.ts'
|
|
2
2
|
export { defineFileTransform } from './lib/files/config.ts'
|
|
3
|
-
export type { AssetServer, AssetServerOptions } from './lib/asset-server.ts'
|
|
3
|
+
export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts'
|
|
4
|
+
export type { ModuleLoader } from './lib/loaders.ts'
|
package/src/lib/access.ts
CHANGED
|
@@ -1,26 +1,407 @@
|
|
|
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
|
type AccessPolicy = {
|
|
8
|
+
getPackageWatchDirectories(): readonly string[]
|
|
9
|
+
handleFileEvent(filePath: string): void
|
|
5
10
|
isAllowed(filePath: string): boolean
|
|
6
11
|
}
|
|
7
12
|
|
|
13
|
+
const packageStateFileNames = new Set([
|
|
14
|
+
'bun.lock',
|
|
15
|
+
'bun.lockb',
|
|
16
|
+
'npm-shrinkwrap.json',
|
|
17
|
+
'package-lock.json',
|
|
18
|
+
'pnpm-lock.yaml',
|
|
19
|
+
'pnpm-workspace.yaml',
|
|
20
|
+
'yarn.lock',
|
|
21
|
+
])
|
|
22
|
+
const packageManagerRootFileNames = packageStateFileNames
|
|
23
|
+
const packageNamePartPattern = /^[A-Za-z0-9._~-]+$/
|
|
24
|
+
|
|
8
25
|
export function createAccessPolicy(options: {
|
|
9
|
-
|
|
10
|
-
|
|
26
|
+
allowFiles: readonly string[]
|
|
27
|
+
allowPackages?: readonly string[]
|
|
28
|
+
denyFiles?: readonly string[]
|
|
29
|
+
packageSearchRoots?: readonly string[]
|
|
11
30
|
rootDir: string
|
|
12
31
|
}): AccessPolicy {
|
|
13
|
-
let allowMatchers = options.
|
|
14
|
-
|
|
32
|
+
let allowMatchers = options.allowFiles.map((pattern) =>
|
|
33
|
+
createFileMatcher(pattern, options.rootDir),
|
|
34
|
+
)
|
|
35
|
+
let allowPackageNames = normalizePackageNames(options.allowPackages, 'allowPackages')
|
|
36
|
+
let denyMatchers = (options.denyFiles ?? []).map((pattern) =>
|
|
15
37
|
createFileMatcher(pattern, options.rootDir),
|
|
16
38
|
)
|
|
39
|
+
let packageSearchRoots = [options.rootDir, ...(options.packageSearchRoots ?? [])]
|
|
40
|
+
let packageRootPaths = createPackageRootPaths({
|
|
41
|
+
allowPackageNames,
|
|
42
|
+
searchRoots: packageSearchRoots,
|
|
43
|
+
})
|
|
44
|
+
let allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths)
|
|
45
|
+
let packageStateDirectories =
|
|
46
|
+
allowPackageNames.size === 0 ? [] : getPackageStateDirectories(packageSearchRoots)
|
|
47
|
+
let packageRootsDirty = false
|
|
48
|
+
|
|
49
|
+
function refreshPackageRootPathTries(): void {
|
|
50
|
+
if (!packageRootsDirty) return
|
|
51
|
+
|
|
52
|
+
packageRootPaths = createPackageRootPaths({
|
|
53
|
+
allowPackageNames,
|
|
54
|
+
searchRoots: packageSearchRoots,
|
|
55
|
+
})
|
|
56
|
+
allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths)
|
|
57
|
+
packageRootsDirty = false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isAllowedPackage(filePath: string): boolean {
|
|
61
|
+
if (allowPackageNames.size === 0) return false
|
|
62
|
+
refreshPackageRootPathTries()
|
|
63
|
+
|
|
64
|
+
return isPathInPackageRootPathTrie(filePath, allowPackageRootPathTrie)
|
|
65
|
+
}
|
|
17
66
|
|
|
18
67
|
return {
|
|
68
|
+
getPackageWatchDirectories() {
|
|
69
|
+
if (allowPackageNames.size === 0) return []
|
|
70
|
+
return packageStateDirectories
|
|
71
|
+
},
|
|
72
|
+
handleFileEvent(filePath) {
|
|
73
|
+
if (allowPackageNames.size === 0) return
|
|
74
|
+
if (!isPackageStateFileEvent(filePath, packageStateDirectories)) return
|
|
75
|
+
|
|
76
|
+
packageRootsDirty = true
|
|
77
|
+
},
|
|
19
78
|
isAllowed(filePath) {
|
|
20
79
|
if (isInjectedPackageFilePath(filePath)) return true
|
|
21
|
-
if (!allowMatchers.some((matcher) => matcher(filePath)))
|
|
80
|
+
if (!allowMatchers.some((matcher) => matcher(filePath)) && !isAllowedPackage(filePath)) {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
22
83
|
if (denyMatchers.length > 0 && denyMatchers.some((matcher) => matcher(filePath))) return false
|
|
23
84
|
return true
|
|
24
85
|
},
|
|
25
86
|
}
|
|
26
87
|
}
|
|
88
|
+
|
|
89
|
+
function normalizePackageNames(
|
|
90
|
+
packageOption: readonly string[] | undefined,
|
|
91
|
+
optionName: 'allowPackages',
|
|
92
|
+
): Set<string> {
|
|
93
|
+
let packageNames = new Set<string>()
|
|
94
|
+
|
|
95
|
+
for (let packageName of packageOption ?? []) {
|
|
96
|
+
if (typeof packageName !== 'string') {
|
|
97
|
+
throw new TypeError(`${optionName} values must be strings`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let normalizedPackageName = packageName.trim()
|
|
101
|
+
if (!isValidPackageName(normalizedPackageName)) {
|
|
102
|
+
throw new TypeError(`${optionName} values must be package names. Received "${packageName}".`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
packageNames.add(normalizedPackageName)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return packageNames
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validatePackageName(packageName: string, message: string): void {
|
|
112
|
+
if (!isValidPackageName(packageName)) {
|
|
113
|
+
throw new TypeError(message)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type PackageJson = {
|
|
118
|
+
dependencies?: Record<string, string>
|
|
119
|
+
optionalDependencies?: Record<string, string>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type PackageRootPathTrie = {
|
|
123
|
+
children: Map<string, PackageRootPathTrie>
|
|
124
|
+
packageRoot: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type PackageRootQueueItem = {
|
|
128
|
+
packageJsonPath: string
|
|
129
|
+
packageName: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function createPackageRootPaths(options: {
|
|
133
|
+
allowPackageNames: ReadonlySet<string>
|
|
134
|
+
searchRoots: readonly string[]
|
|
135
|
+
}): Set<string> {
|
|
136
|
+
let allowPackageRootPaths = new Set<string>()
|
|
137
|
+
let allowQueue: PackageRootQueueItem[] = []
|
|
138
|
+
let seenAllowedPackageRoots = new Set<string>()
|
|
139
|
+
let searchRoots = normalizePackageSearchRoots(options.searchRoots)
|
|
140
|
+
|
|
141
|
+
for (let packageName of options.allowPackageNames) {
|
|
142
|
+
let foundPackage = false
|
|
143
|
+
|
|
144
|
+
for (let searchRoot of searchRoots) {
|
|
145
|
+
let packageJsonPath = findPackageJsonPath(packageName, searchRoot)
|
|
146
|
+
if (packageJsonPath === null) continue
|
|
147
|
+
|
|
148
|
+
foundPackage = true
|
|
149
|
+
allowQueue.push({ packageJsonPath, packageName })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!foundPackage) {
|
|
153
|
+
throw new TypeError(`Could not resolve allowed package "${packageName}".`)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
while (allowQueue.length > 0) {
|
|
158
|
+
let { packageJsonPath } = allowQueue.shift()!
|
|
159
|
+
let packageRootPath = normalizeFilePath(path.dirname(packageJsonPath))
|
|
160
|
+
if (seenAllowedPackageRoots.has(packageRootPath)) continue
|
|
161
|
+
seenAllowedPackageRoots.add(packageRootPath)
|
|
162
|
+
|
|
163
|
+
let packageJson = readPackageJson(packageJsonPath)
|
|
164
|
+
allowPackageRootPaths.add(packageRootPath)
|
|
165
|
+
|
|
166
|
+
for (let dependencyName of Object.keys(packageJson.dependencies ?? {})) {
|
|
167
|
+
validatePackageName(
|
|
168
|
+
dependencyName,
|
|
169
|
+
`Dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`,
|
|
170
|
+
)
|
|
171
|
+
let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath)
|
|
172
|
+
if (dependencyPackageJsonPath === null) {
|
|
173
|
+
throw new TypeError(
|
|
174
|
+
`Could not resolve dependency "${dependencyName}" from ${packageJsonPath}.`,
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
allowQueue.push({
|
|
178
|
+
packageJsonPath: dependencyPackageJsonPath,
|
|
179
|
+
packageName: dependencyName,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
for (let dependencyName of Object.keys(packageJson.optionalDependencies ?? {})) {
|
|
183
|
+
validatePackageName(
|
|
184
|
+
dependencyName,
|
|
185
|
+
`Optional dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`,
|
|
186
|
+
)
|
|
187
|
+
let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath)
|
|
188
|
+
if (dependencyPackageJsonPath !== null) {
|
|
189
|
+
allowQueue.push({
|
|
190
|
+
packageJsonPath: dependencyPackageJsonPath,
|
|
191
|
+
packageName: dependencyName,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return allowPackageRootPaths
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function createPackageRootPathTrie(packageRootPaths: ReadonlySet<string>): PackageRootPathTrie {
|
|
201
|
+
let rootNode = createPackageRootPathTrieNode()
|
|
202
|
+
|
|
203
|
+
for (let packageRootPath of packageRootPaths) {
|
|
204
|
+
let node = rootNode
|
|
205
|
+
for (let segment of getFilePathSegments(packageRootPath)) {
|
|
206
|
+
let childNode = node.children.get(segment)
|
|
207
|
+
if (!childNode) {
|
|
208
|
+
childNode = createPackageRootPathTrieNode()
|
|
209
|
+
node.children.set(segment, childNode)
|
|
210
|
+
}
|
|
211
|
+
node = childNode
|
|
212
|
+
}
|
|
213
|
+
node.packageRoot = true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return rootNode
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createPackageRootPathTrieNode(): PackageRootPathTrie {
|
|
220
|
+
return {
|
|
221
|
+
children: new Map(),
|
|
222
|
+
packageRoot: false,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isPathInPackageRootPathTrie(filePath: string, trie: PackageRootPathTrie): boolean {
|
|
227
|
+
let node = trie
|
|
228
|
+
if (node.packageRoot) return true
|
|
229
|
+
|
|
230
|
+
for (let segment of getFilePathSegments(filePath)) {
|
|
231
|
+
let childNode = node.children.get(segment)
|
|
232
|
+
if (!childNode) return false
|
|
233
|
+
if (childNode.packageRoot) return true
|
|
234
|
+
node = childNode
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getFilePathSegments(filePath: string): string[] {
|
|
241
|
+
return normalizeFilePath(filePath).split('/')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function normalizePackageSearchRoots(searchRoots: readonly string[]): string[] {
|
|
245
|
+
let normalizedSearchRoots = new Set<string>()
|
|
246
|
+
|
|
247
|
+
for (let searchRoot of searchRoots) {
|
|
248
|
+
let normalizedSearchRoot = normalizeFilePath(searchRoot)
|
|
249
|
+
normalizedSearchRoots.add(normalizedSearchRoot)
|
|
250
|
+
|
|
251
|
+
if (path.basename(normalizedSearchRoot) === 'node_modules') {
|
|
252
|
+
normalizedSearchRoots.add(path.dirname(normalizedSearchRoot))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return [...normalizedSearchRoots]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getPackageStateDirectories(searchRoots: readonly string[]): string[] {
|
|
260
|
+
let packageStateDirectories = new Set<string>()
|
|
261
|
+
|
|
262
|
+
for (let searchRoot of searchRoots) {
|
|
263
|
+
let packageManagerRoot = findPackageManagerRoot(searchRoot)
|
|
264
|
+
if (packageManagerRoot !== null) {
|
|
265
|
+
packageStateDirectories.add(packageManagerRoot)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return [...packageStateDirectories]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isPackageStateFileEvent(
|
|
273
|
+
filePath: string,
|
|
274
|
+
packageStateDirectories: readonly string[],
|
|
275
|
+
): boolean {
|
|
276
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
277
|
+
let fileName = path.basename(normalizedFilePath)
|
|
278
|
+
if (!packageStateFileNames.has(fileName)) return false
|
|
279
|
+
|
|
280
|
+
let directory = path.dirname(normalizedFilePath)
|
|
281
|
+
return packageStateDirectories.some(
|
|
282
|
+
(packageStateDirectory) => directory === packageStateDirectory,
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function findPackageManagerRoot(startDirectory: string): string | null {
|
|
287
|
+
let directory = normalizePackageStateSearchRoot(startDirectory)
|
|
288
|
+
|
|
289
|
+
while (true) {
|
|
290
|
+
if (hasPackageManagerRootFile(directory)) return directory
|
|
291
|
+
|
|
292
|
+
let parentDirectory = path.dirname(directory)
|
|
293
|
+
if (parentDirectory === directory) return null
|
|
294
|
+
directory = parentDirectory
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizePackageStateSearchRoot(searchRoot: string): string {
|
|
299
|
+
let normalizedSearchRoot = normalizeFilePath(searchRoot)
|
|
300
|
+
return path.basename(normalizedSearchRoot) === 'node_modules'
|
|
301
|
+
? path.dirname(normalizedSearchRoot)
|
|
302
|
+
: normalizedSearchRoot
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function hasPackageManagerRootFile(directory: string): boolean {
|
|
306
|
+
for (let fileName of packageManagerRootFileNames) {
|
|
307
|
+
try {
|
|
308
|
+
let stat = fs.statSync(path.join(directory, fileName))
|
|
309
|
+
if (stat.isFile()) return true
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (isPathNotFoundError(error)) continue
|
|
312
|
+
throw error
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return false
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function findPackageJsonPath(packageName: string, startDirectory: string): string | null {
|
|
320
|
+
let directory = normalizeFilePath(startDirectory)
|
|
321
|
+
|
|
322
|
+
while (true) {
|
|
323
|
+
let packageJsonPath = resolvePackageJsonPath(directory, packageName)
|
|
324
|
+
if (packageJsonPath !== null) return packageJsonPath
|
|
325
|
+
|
|
326
|
+
let parentDirectory = path.dirname(directory)
|
|
327
|
+
if (parentDirectory === directory) return null
|
|
328
|
+
directory = parentDirectory
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolvePackageJsonPath(directory: string, packageName: string): string | null {
|
|
333
|
+
let packagePath =
|
|
334
|
+
path.basename(directory) === 'node_modules'
|
|
335
|
+
? path.join(directory, packageName, 'package.json')
|
|
336
|
+
: path.join(directory, 'node_modules', packageName, 'package.json')
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
return normalizeFilePath(fs.realpathSync(packagePath))
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (isPathNotFoundError(error)) return null
|
|
342
|
+
throw error
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function readPackageJson(packageJsonPath: string): PackageJson {
|
|
347
|
+
let packageJson: unknown
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as unknown
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (isPathNotFoundError(error)) return {}
|
|
353
|
+
throw error
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (packageJson === null || typeof packageJson !== 'object') {
|
|
357
|
+
return {}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
dependencies: readDependencyMap(packageJson, 'dependencies'),
|
|
362
|
+
optionalDependencies: readDependencyMap(packageJson, 'optionalDependencies'),
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function readDependencyMap(packageJson: object, key: string): Record<string, string> | undefined {
|
|
367
|
+
if (!(key in packageJson)) return undefined
|
|
368
|
+
|
|
369
|
+
let value = packageJson[key as keyof typeof packageJson]
|
|
370
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
371
|
+
return undefined
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let dependencies: Record<string, string> = {}
|
|
375
|
+
for (let [dependencyName, dependencyVersion] of Object.entries(value)) {
|
|
376
|
+
if (typeof dependencyVersion === 'string') {
|
|
377
|
+
dependencies[dependencyName] = dependencyVersion
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return dependencies
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isValidPackageName(packageName: string): boolean {
|
|
385
|
+
if (packageName.length === 0) return false
|
|
386
|
+
|
|
387
|
+
let packageNameParts = packageName.startsWith('@')
|
|
388
|
+
? packageName.slice(1).split('/')
|
|
389
|
+
: packageName.split('/')
|
|
390
|
+
|
|
391
|
+
if (packageNameParts.length !== (packageName.startsWith('@') ? 2 : 1)) return false
|
|
392
|
+
|
|
393
|
+
return packageNameParts.every(
|
|
394
|
+
(part) => part.length > 0 && part !== '.' && part !== '..' && packageNamePartPattern.test(part),
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isPathNotFoundError(
|
|
399
|
+
error: unknown,
|
|
400
|
+
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
401
|
+
return (
|
|
402
|
+
error instanceof Error &&
|
|
403
|
+
'code' in error &&
|
|
404
|
+
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
405
|
+
(error as NodeJS.ErrnoException).code === 'ENOTDIR')
|
|
406
|
+
)
|
|
407
|
+
}
|