@remix-run/assets 0.4.4 → 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.
Files changed (74) hide show
  1. package/README.md +322 -56
  2. package/dist/assets.d.ts +2 -1
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/assets.js +2 -2
  5. package/dist/lib/access.d.ts +6 -2
  6. package/dist/lib/access.d.ts.map +1 -1
  7. package/dist/lib/access.js +300 -5
  8. package/dist/lib/asset-server.d.ts +118 -5
  9. package/dist/lib/asset-server.d.ts.map +1 -1
  10. package/dist/lib/asset-server.js +260 -24
  11. package/dist/lib/compilation-error.d.ts +1 -1
  12. package/dist/lib/compilation-error.d.ts.map +1 -1
  13. package/dist/lib/file-matcher.js +1 -1
  14. package/dist/lib/files/compiler.d.ts.map +1 -1
  15. package/dist/lib/files/compiler.js +7 -6
  16. package/dist/lib/files/config.js +1 -1
  17. package/dist/lib/hmr.d.ts +37 -0
  18. package/dist/lib/hmr.d.ts.map +1 -0
  19. package/dist/lib/hmr.js +357 -0
  20. package/dist/lib/injected-packages.d.ts.map +1 -1
  21. package/dist/lib/injected-packages.js +36 -13
  22. package/dist/lib/loaders.d.ts +57 -0
  23. package/dist/lib/loaders.d.ts.map +1 -0
  24. package/dist/lib/loaders.js +1 -0
  25. package/dist/lib/module-store.d.ts +24 -0
  26. package/dist/lib/module-store.d.ts.map +1 -1
  27. package/dist/lib/module-store.js +136 -11
  28. package/dist/lib/routes.js +1 -1
  29. package/dist/lib/scripts/compiler.d.ts +24 -1
  30. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  31. package/dist/lib/scripts/compiler.js +183 -29
  32. package/dist/lib/scripts/conditions.d.ts +2 -0
  33. package/dist/lib/scripts/conditions.d.ts.map +1 -0
  34. package/dist/lib/scripts/conditions.js +1 -0
  35. package/dist/lib/scripts/emit.d.ts +3 -0
  36. package/dist/lib/scripts/emit.d.ts.map +1 -1
  37. package/dist/lib/scripts/emit.js +24 -5
  38. package/dist/lib/scripts/resolve.d.ts +4 -0
  39. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  40. package/dist/lib/scripts/resolve.js +70 -5
  41. package/dist/lib/scripts/transform.d.ts +9 -0
  42. package/dist/lib/scripts/transform.d.ts.map +1 -1
  43. package/dist/lib/scripts/transform.js +222 -18
  44. package/dist/lib/source-maps.js +1 -1
  45. package/dist/lib/styles/compiler.d.ts +14 -1
  46. package/dist/lib/styles/compiler.d.ts.map +1 -1
  47. package/dist/lib/styles/compiler.js +78 -14
  48. package/dist/lib/styles/emit.js +4 -4
  49. package/dist/lib/styles/resolve.d.ts.map +1 -1
  50. package/dist/lib/styles/resolve.js +8 -7
  51. package/dist/lib/styles/transform.js +3 -3
  52. package/dist/lib/target.d.ts +1 -1
  53. package/dist/lib/target.d.ts.map +1 -1
  54. package/dist/lib/watch.js +1 -1
  55. package/dist/types/hmr.d.ts +36 -0
  56. package/package.json +17 -11
  57. package/src/assets.ts +2 -1
  58. package/src/lib/access.ts +386 -5
  59. package/src/lib/asset-server.ts +429 -18
  60. package/src/lib/compilation-error.ts +1 -0
  61. package/src/lib/files/compiler.ts +7 -3
  62. package/src/lib/hmr.ts +397 -0
  63. package/src/lib/injected-packages.ts +41 -11
  64. package/src/lib/loaders.ts +80 -0
  65. package/src/lib/module-store.ts +190 -9
  66. package/src/lib/scripts/compiler.ts +248 -24
  67. package/src/lib/scripts/conditions.ts +1 -0
  68. package/src/lib/scripts/emit.ts +50 -5
  69. package/src/lib/scripts/resolve.ts +124 -2
  70. package/src/lib/scripts/transform.ts +290 -19
  71. package/src/lib/styles/compiler.ts +108 -8
  72. package/src/lib/styles/emit.ts +1 -1
  73. package/src/lib/styles/resolve.ts +11 -7
  74. package/src/types/hmr.d.ts +36 -0
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
- allow: readonly string[]
10
- deny?: readonly string[]
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.allow.map((pattern) => createFileMatcher(pattern, options.rootDir))
14
- let denyMatchers = (options.deny ?? []).map((pattern) =>
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))) return false
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
+ }