@remix-run/assets 0.5.0 → 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.
Files changed (40) hide show
  1. package/README.md +85 -92
  2. package/dist/assets.d.ts +2 -0
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/lib/access.d.ts +29 -2
  5. package/dist/lib/access.d.ts.map +1 -1
  6. package/dist/lib/access.js +52 -26
  7. package/dist/lib/asset-server.d.ts +21 -7
  8. package/dist/lib/asset-server.d.ts.map +1 -1
  9. package/dist/lib/asset-server.js +30 -15
  10. package/dist/lib/compilation-error.d.ts +1 -1
  11. package/dist/lib/compilation-error.d.ts.map +1 -1
  12. package/dist/lib/files/compiler.js +2 -2
  13. package/dist/lib/injected-packages.d.ts +3 -2
  14. package/dist/lib/injected-packages.d.ts.map +1 -1
  15. package/dist/lib/injected-packages.js +11 -8
  16. package/dist/lib/inspection.d.ts +39 -0
  17. package/dist/lib/inspection.d.ts.map +1 -0
  18. package/dist/lib/inspection.js +160 -0
  19. package/dist/lib/routes.d.ts +11 -3
  20. package/dist/lib/routes.d.ts.map +1 -1
  21. package/dist/lib/routes.js +124 -80
  22. package/dist/lib/scripts/compiler.js +2 -2
  23. package/dist/lib/scripts/resolve.js +9 -9
  24. package/dist/lib/scripts/transform.js +2 -2
  25. package/dist/lib/styles/resolve.js +8 -8
  26. package/dist/lib/styles/transform.js +2 -2
  27. package/package.json +5 -6
  28. package/src/assets.ts +2 -0
  29. package/src/lib/access.ts +83 -30
  30. package/src/lib/asset-server.ts +50 -22
  31. package/src/lib/compilation-error.ts +3 -3
  32. package/src/lib/files/compiler.ts +2 -2
  33. package/src/lib/injected-packages.ts +16 -10
  34. package/src/lib/inspection.ts +229 -0
  35. package/src/lib/routes.ts +158 -126
  36. package/src/lib/scripts/compiler.ts +2 -2
  37. package/src/lib/scripts/resolve.ts +9 -9
  38. package/src/lib/scripts/transform.ts +2 -2
  39. package/src/lib/styles/resolve.ts +8 -8
  40. package/src/lib/styles/transform.ts +2 -2
package/README.md CHANGED
@@ -6,9 +6,9 @@ Fetch-based server for compiling browser assets on demand.
6
6
 
7
7
  - **On-Demand Compilation** - Compile browser scripts and styles on demand
8
8
  - **File Serving** - Serve configured file assets like images and fonts with optional transforms
9
- - **Custom File Mapping** - Define patterns for mapping public URLs to file paths on disk
10
9
  - **Access Control** - Control exactly which files and packages can be served
11
10
  - **Preloads** - Generate preload URLs for scripts and styles based on imports
11
+ - **Inspection** - List browser-reachable assets and explain URL-to-file mappings
12
12
  - **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
13
13
  - **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
14
14
  - **Source Maps** - Serve inline or external sourcemaps
@@ -21,6 +21,12 @@ Fetch-based server for compiling browser assets on demand.
21
21
  npm i remix
22
22
  ```
23
23
 
24
+ The optional image transform examples also use Sharp:
25
+
26
+ ```sh
27
+ npm i sharp
28
+ ```
29
+
24
30
  ## Usage
25
31
 
26
32
  Use `createAssetServer` to serve browser assets from a URL namespace in your app.
@@ -31,10 +37,6 @@ import { createAssetServer } from 'remix/assets'
31
37
 
32
38
  let assetServer = createAssetServer({
33
39
  basePath: '/assets',
34
- fileMap: {
35
- '/app/*path': 'app/*path',
36
- '/npm/*path': 'node_modules/*path',
37
- },
38
40
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
39
41
  allowPackages: ['remix'],
40
42
  files: {
@@ -51,6 +53,64 @@ router.get('/assets/*', ({ request }) => {
51
53
 
52
54
  This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
53
55
 
56
+ ## Shared Configuration
57
+
58
+ Keep JSON-compatible asset mapping, access, and file-type settings in `remix.json` so the running
59
+ server and Remix CLI use the same configuration:
60
+
61
+ ```jsonc
62
+ {
63
+ "$schema": "./node_modules/remix/schema/remix.json",
64
+ "assets": {
65
+ "basePath": "/assets",
66
+ "mounts": {
67
+ "app": "app",
68
+ "npm": "node_modules",
69
+ },
70
+ "allowFiles": ["app/routes.ts", "app/**/public/**"],
71
+ "allowPackages": ["remix"],
72
+ "denyFiles": ["app/**/*.test.*"],
73
+ "files": {
74
+ "extensions": [".svg", ".png", ".jpg", ".woff2"],
75
+ },
76
+ },
77
+ }
78
+ ```
79
+
80
+ Load it from application code and add runtime-only behavior there:
81
+
82
+ ```ts
83
+ import { createAssetServer, defineFileTransform } from 'remix/assets'
84
+ import { loadConfig } from 'remix/cli'
85
+ import sharp from 'sharp'
86
+
87
+ let config = await loadConfig(import.meta.dirname)
88
+ if (config.assets === undefined) throw new Error('Missing assets configuration')
89
+ if (config.assets.files === undefined) throw new Error('Missing asset file configuration')
90
+
91
+ let assetServer = createAssetServer({
92
+ ...config.assets,
93
+ files: {
94
+ ...config.assets.files,
95
+ transforms: {
96
+ webp: defineFileTransform({
97
+ extensions: ['.png', '.jpg'],
98
+ async transform(bytes) {
99
+ return {
100
+ content: await sharp(bytes).webp({ quality: 80 }).toBuffer(),
101
+ extension: '.webp',
102
+ }
103
+ },
104
+ }),
105
+ },
106
+ },
107
+ })
108
+ ```
109
+
110
+ `loadConfig()` accepts either a config file or a directory. When given a directory, it searches
111
+ upward for the nearest `remix.json`. Run `remix assets` to list reachable files, or
112
+ `remix assets inspect <url-or-file>` to inspect one mapping and its access decision.
113
+
54
114
  ## Root Directory
55
115
 
56
116
  Use `rootDir` to specify the root directory of the asset server, which is used to resolve relative file paths. Defaults to `process.cwd()`.
@@ -62,10 +122,6 @@ import { createAssetServer } from 'remix/assets'
62
122
  let assetServer = createAssetServer({
63
123
  rootDir: path.resolve(import.meta.dirname, '..'),
64
124
  basePath: '/assets',
65
- fileMap: {
66
- '/app/*path': 'app/*path',
67
- '/npm/*path': 'node_modules/*path',
68
- },
69
125
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
70
126
  allowPackages: ['remix'],
71
127
  })
@@ -80,10 +136,6 @@ import { createAssetServer } from 'remix/assets'
80
136
 
81
137
  let assetServer = createAssetServer({
82
138
  basePath: '/assets',
83
- fileMap: {
84
- '/app/*path': 'app/*path',
85
- '/npm/*path': 'node_modules/*path',
86
- },
87
139
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
88
140
  allowPackages: ['remix'],
89
141
  denyFiles: ['app/**/*.server.*'],
@@ -92,28 +144,26 @@ let assetServer = createAssetServer({
92
144
 
93
145
  Values for `allowFiles` and `denyFiles` are file paths or globs. Relative values are resolved from `rootDir`. Absolute file paths match exactly, and absolute directory paths also match their descendants.
94
146
 
95
- Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `fileMap`.
147
+ Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `mounts`.
96
148
 
97
- ## File Map
149
+ ## Mounts
98
150
 
99
- Use `fileMap` to map public URLs to file paths on disk. `basePath` defines the shared public mount point, and the `fileMap` keys are URL patterns relative to that base path. The values are root-relative file path patterns.
151
+ By default, the asset server mounts the `app` directory at `/app` and `node_modules` at `/npm`. Use `mounts` to replace these defaults. Keys are public paths relative to `basePath`, and values are directory paths relative to `rootDir`.
100
152
 
101
153
  ```ts
102
154
  import { createAssetServer } from 'remix/assets'
103
155
 
104
156
  let assetServer = createAssetServer({
105
157
  basePath: '/assets',
106
- fileMap: {
107
- '/app/*path': 'app/*path',
108
- '/npm/*path': 'node_modules/*path',
158
+ mounts: {
159
+ source: 'app',
160
+ vendor: 'node_modules',
109
161
  },
110
162
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
111
163
  allowPackages: ['remix'],
112
164
  })
113
165
  ```
114
166
 
115
- `fileMap` entries use [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) syntax for both URL and file patterns. Wildcards must be named, and the same params must appear in both patterns so imports can be rewritten back to public URLs. For example, with `basePath: '/assets'`, a `fileMap` key of `'/app/*path'` is served at `/assets/app/*path`.
116
-
117
167
  ### File watching
118
168
 
119
169
  The file system is watched by default so source changes are picked up without requiring a server restart.
@@ -123,10 +173,6 @@ import { createAssetServer } from 'remix/assets'
123
173
 
124
174
  let assetServer = createAssetServer({
125
175
  basePath: '/assets',
126
- fileMap: {
127
- '/app/*path': 'app/*path',
128
- '/npm/*path': 'node_modules/*path',
129
- },
130
176
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
131
177
  allowPackages: ['remix'],
132
178
  })
@@ -145,10 +191,6 @@ import { createAssetServer } from 'remix/assets'
145
191
 
146
192
  let assetServer = createAssetServer({
147
193
  basePath: '/assets',
148
- fileMap: {
149
- '/app/*path': 'app/*path',
150
- '/npm/*path': 'node_modules/*path',
151
- },
152
194
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
153
195
  allowPackages: ['remix'],
154
196
  watch: false,
@@ -162,10 +204,6 @@ import { createAssetServer } from 'remix/assets'
162
204
 
163
205
  let assetServer = createAssetServer({
164
206
  basePath: '/assets',
165
- fileMap: {
166
- '/app/*path': 'app/*path',
167
- '/npm/*path': 'node_modules/*path',
168
- },
169
207
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
170
208
  allowPackages: ['remix'],
171
209
  watch: {
@@ -183,6 +221,20 @@ let src = await assetServer.getHref('app/actions/public/entry.ts')
183
221
  // '/assets/app/actions/public/entry.ts'
184
222
  ```
185
223
 
224
+ ## Inspection
225
+
226
+ Use `getAssets()` for a sorted list of files that are currently browser-reachable through the
227
+ asset server. Use `getAssetDetails()` with a public URL or file path to inspect its mapping, file
228
+ type, access rules, and reachability status.
229
+
230
+ ```ts
231
+ let assets = await assetServer.getAssets()
232
+ // [{ url: '/assets/app/actions/public/entry.ts', filePath: '/project/app/actions/public/entry.ts', ... }]
233
+
234
+ let details = await assetServer.getAssetDetails('/assets/app/actions/public/entry.ts')
235
+ // { status: 'reachable', type: 'script', ... }
236
+ ```
237
+
186
238
  For configured `files` assets, you can also pass a `transform` pipeline to build a request URL with custom file transforms. Basic transforms are written as strings, while dynamic transforms use `[name, param]` tuples.
187
239
 
188
240
  ```ts
@@ -221,10 +273,6 @@ import { createAssetServer } from 'remix/assets'
221
273
 
222
274
  let assetServer = createAssetServer({
223
275
  basePath: '/assets',
224
- fileMap: {
225
- '/app/*path': 'app/*path',
226
- '/npm/*path': 'node_modules/*path',
227
- },
228
276
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
229
277
  allowPackages: ['remix'],
230
278
  watch: false,
@@ -247,10 +295,6 @@ import { createAssetServer } from 'remix/assets'
247
295
 
248
296
  let assetServer = createAssetServer({
249
297
  basePath: '/assets',
250
- fileMap: {
251
- '/app/*path': 'app/*path',
252
- '/npm/*path': 'node_modules/*path',
253
- },
254
298
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
255
299
  allowPackages: ['remix'],
256
300
  target: {
@@ -272,10 +316,6 @@ import { createAssetServer } from 'remix/assets'
272
316
 
273
317
  let assetServer = createAssetServer({
274
318
  basePath: '/assets',
275
- fileMap: {
276
- '/app/*path': 'app/*path',
277
- '/npm/*path': 'node_modules/*path',
278
- },
279
319
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
280
320
  allowPackages: ['remix'],
281
321
  sourceMaps: 'external',
@@ -289,10 +329,6 @@ import { createAssetServer } from 'remix/assets'
289
329
 
290
330
  let assetServer = createAssetServer({
291
331
  basePath: '/assets',
292
- fileMap: {
293
- '/app/*path': 'app/*path',
294
- '/npm/*path': 'node_modules/*path',
295
- },
296
332
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
297
333
  allowPackages: ['remix'],
298
334
  sourceMaps: 'inline',
@@ -309,10 +345,6 @@ import { createAssetServer } from 'remix/assets'
309
345
 
310
346
  let assetServer = createAssetServer({
311
347
  basePath: '/assets',
312
- fileMap: {
313
- '/app/*path': 'app/*path',
314
- '/npm/*path': 'node_modules/*path',
315
- },
316
348
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
317
349
  allowPackages: ['remix'],
318
350
  minify: true,
@@ -330,10 +362,6 @@ import { createAssetServer } from 'remix/assets'
330
362
 
331
363
  let assetServer = createAssetServer({
332
364
  basePath: '/assets',
333
- fileMap: {
334
- '/app/*path': 'app/*path',
335
- '/npm/*path': 'node_modules/*path',
336
- },
337
365
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
338
366
  allowPackages: ['remix'],
339
367
  scripts: {
@@ -355,10 +383,6 @@ import { createAssetServer } from 'remix/assets'
355
383
 
356
384
  let assetServer = createAssetServer({
357
385
  basePath: '/assets',
358
- fileMap: {
359
- '/app/*path': 'app/*path',
360
- '/npm/*path': 'node_modules/*path',
361
- },
362
386
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
363
387
  allowPackages: ['remix'],
364
388
  scripts: {
@@ -376,7 +400,6 @@ import { createAssetServer } from 'remix/assets'
376
400
 
377
401
  let assetServer = createAssetServer({
378
402
  basePath: '/assets',
379
- fileMap: { '/app/*path': 'app/*path' },
380
403
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
381
404
  denyFiles: ['app/**/*.test.*'],
382
405
  scripts: {
@@ -404,10 +427,6 @@ import { createAssetServer } from 'remix/assets'
404
427
 
405
428
  let assetServer = createAssetServer({
406
429
  basePath: '/assets',
407
- fileMap: {
408
- '/app/*path': 'app/*path',
409
- '/npm/*path': 'node_modules/*path',
410
- },
411
430
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
412
431
  allowPackages: ['remix'],
413
432
  files: {
@@ -430,10 +449,6 @@ import sharp from 'sharp'
430
449
 
431
450
  let assetServer = createAssetServer({
432
451
  basePath: '/assets',
433
- fileMap: {
434
- '/app/*path': 'app/*path',
435
- '/npm/*path': 'node_modules/*path',
436
- },
437
452
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
438
453
  allowPackages: ['remix'],
439
454
  files: {
@@ -464,10 +479,6 @@ import { createAssetServer, defineFileTransform } from 'remix/assets'
464
479
 
465
480
  let assetServer = createAssetServer({
466
481
  basePath: '/assets',
467
- fileMap: {
468
- '/app/*path': 'app/*path',
469
- '/npm/*path': 'node_modules/*path',
470
- },
471
482
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
472
483
  allowPackages: ['remix'],
473
484
  files: {
@@ -512,10 +523,6 @@ import { optimize as optimizeSvg } from 'svgo'
512
523
 
513
524
  let assetServer = createAssetServer({
514
525
  basePath: '/assets',
515
- fileMap: {
516
- '/app/*path': 'app/*path',
517
- '/npm/*path': 'node_modules/*path',
518
- },
519
526
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
520
527
  allowPackages: ['remix'],
521
528
  files: {
@@ -548,10 +555,6 @@ import { createFsFileStorage } from 'remix/file-storage/fs'
548
555
 
549
556
  let assetServer = createAssetServer({
550
557
  basePath: '/assets',
551
- fileMap: {
552
- '/app/*path': 'app/*path',
553
- '/npm/*path': 'node_modules/*path',
554
- },
555
558
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
556
559
  allowPackages: ['remix'],
557
560
  files: {
@@ -573,10 +576,6 @@ import { createAssetServer } from 'remix/assets'
573
576
 
574
577
  let assetServer = createAssetServer({
575
578
  basePath: '/assets',
576
- fileMap: {
577
- '/app/*path': 'app/*path',
578
- '/npm/*path': 'node_modules/*path',
579
- },
580
579
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
581
580
  allowPackages: ['remix'],
582
581
  files: {
@@ -624,10 +623,6 @@ import { createAssetServer } from 'remix/assets'
624
623
 
625
624
  let assetServer = createAssetServer({
626
625
  basePath: '/assets',
627
- fileMap: {
628
- '/app/*path': 'app/*path',
629
- '/npm/*path': 'node_modules/*path',
630
- },
631
626
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
632
627
  allowPackages: ['remix'],
633
628
  onError(error) {
@@ -651,7 +646,6 @@ import { createAssetServer } from 'remix/assets'
651
646
  let isDevelopment = process.env.NODE_ENV === 'development'
652
647
  let assetServer = createAssetServer({
653
648
  basePath: '/assets',
654
- fileMap: { '/app/*path': 'app/*path' },
655
649
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
656
650
  denyFiles: ['app/**/*.test.*'],
657
651
  hmr: isDevelopment
@@ -798,7 +792,6 @@ if (import.meta.hot) {
798
792
  - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
799
793
  - [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) - Provides the server-side `import.meta.hot` runtime and browser HMR channel used by `hmr`
800
794
  - [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
801
- - [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
802
795
 
803
796
  ## License
804
797
 
package/dist/assets.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { createAssetServer } from './lib/asset-server.ts';
2
2
  export { defineFileTransform } from './lib/files/config.ts';
3
+ export type { AssetAccessDetails, AssetAccessRule } from './lib/access.ts';
3
4
  export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts';
5
+ export type { AssetDetails, AssetKind, AssetStatus } from './lib/inspection.ts';
4
6
  export type { ModuleLoader } from './lib/loaders.ts';
5
7
  //# sourceMappingURL=assets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC/E,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
@@ -1,6 +1,34 @@
1
- type AccessPolicy = {
1
+ /** Access-policy result for an inspected asset file. */
2
+ export interface AssetAccessDetails {
3
+ /** Whether the asset server may serve the file. */
4
+ allowed: boolean;
5
+ /** The first configured rule that allowed the file, when one matched. */
6
+ allowedBy?: AssetAccessRule;
7
+ /** The first matching `denyFiles` pattern, when access was denied. */
8
+ deniedBy?: string;
9
+ }
10
+ /** Rule that allows an inspected asset file to be served. */
11
+ export type AssetAccessRule =
12
+ /** A matching `allowFiles` entry. */
13
+ {
14
+ kind: 'file';
15
+ value: string;
16
+ }
17
+ /** A runtime file provided internally by the asset server. */
18
+ | {
19
+ kind: 'injected';
20
+ value: string;
21
+ }
22
+ /** A matching `allowPackages` entry. */
23
+ | {
24
+ kind: 'package';
25
+ value: string;
26
+ };
27
+ export type AccessPolicy = {
28
+ getAllowedPackageRoots(): readonly string[];
2
29
  getPackageWatchDirectories(): readonly string[];
3
30
  handleFileEvent(filePath: string): void;
31
+ inspect(filePath: string): AssetAccessDetails;
4
32
  isAllowed(filePath: string): boolean;
5
33
  };
6
34
  export declare function createAccessPolicy(options: {
@@ -10,5 +38,4 @@ export declare function createAccessPolicy(options: {
10
38
  packageSearchRoots?: readonly string[];
11
39
  rootDir: string;
12
40
  }): AccessPolicy;
13
- export {};
14
41
  //# sourceMappingURL=access.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;IAClB,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAAA;IAC/C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAwDf"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,6DAA6D;AAC7D,MAAM,MAAM,eAAe;AACzB,qCAAqC;AACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACjC,8DAA8D;GAC5D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACrC,wCAAwC;GACtC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,YAAY,GAAG;IACzB,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAC3C,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAAA;IAC/C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAC7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAoFf"}
@@ -15,9 +15,15 @@ const packageStateFileNames = new Set([
15
15
  const packageManagerRootFileNames = packageStateFileNames;
16
16
  const packageNamePartPattern = /^[A-Za-z0-9._~-]+$/;
17
17
  export function createAccessPolicy(options) {
18
- let allowMatchers = options.allowFiles.map((pattern) => createFileMatcher(pattern, options.rootDir));
18
+ let allowMatchers = options.allowFiles.map((pattern) => ({
19
+ matcher: createFileMatcher(pattern, options.rootDir),
20
+ pattern,
21
+ }));
19
22
  let allowPackageNames = normalizePackageNames(options.allowPackages, 'allowPackages');
20
- let denyMatchers = (options.denyFiles ?? []).map((pattern) => createFileMatcher(pattern, options.rootDir));
23
+ let denyMatchers = (options.denyFiles ?? []).map((pattern) => ({
24
+ matcher: createFileMatcher(pattern, options.rootDir),
25
+ pattern,
26
+ }));
21
27
  let packageSearchRoots = [options.rootDir, ...(options.packageSearchRoots ?? [])];
22
28
  let packageRootPaths = createPackageRootPaths({
23
29
  allowPackageNames,
@@ -36,13 +42,40 @@ export function createAccessPolicy(options) {
36
42
  allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths);
37
43
  packageRootsDirty = false;
38
44
  }
39
- function isAllowedPackage(filePath) {
45
+ function getAllowedPackageName(filePath) {
40
46
  if (allowPackageNames.size === 0)
41
- return false;
47
+ return undefined;
42
48
  refreshPackageRootPathTries();
43
- return isPathInPackageRootPathTrie(filePath, allowPackageRootPathTrie);
49
+ return getPackageNameFromRootPathTrie(filePath, allowPackageRootPathTrie);
50
+ }
51
+ function inspect(filePath) {
52
+ if (isInjectedPackageFilePath(filePath)) {
53
+ return { allowed: true, allowedBy: { kind: 'injected', value: '@remix-run/assets' } };
54
+ }
55
+ let allowedBy;
56
+ let allowMatch = allowMatchers.find(({ matcher }) => matcher(filePath));
57
+ if (allowMatch) {
58
+ allowedBy = { kind: 'file', value: allowMatch.pattern };
59
+ }
60
+ else {
61
+ let packageName = getAllowedPackageName(filePath);
62
+ if (packageName !== undefined) {
63
+ allowedBy = { kind: 'package', value: packageName };
64
+ }
65
+ }
66
+ if (!allowedBy)
67
+ return { allowed: false };
68
+ let denyMatch = denyMatchers.find(({ matcher }) => matcher(filePath));
69
+ if (denyMatch) {
70
+ return { allowed: false, allowedBy, deniedBy: denyMatch.pattern };
71
+ }
72
+ return { allowed: true, allowedBy };
44
73
  }
45
74
  return {
75
+ getAllowedPackageRoots() {
76
+ refreshPackageRootPathTries();
77
+ return [...packageRootPaths.keys()];
78
+ },
46
79
  getPackageWatchDirectories() {
47
80
  if (allowPackageNames.size === 0)
48
81
  return [];
@@ -55,15 +88,9 @@ export function createAccessPolicy(options) {
55
88
  return;
56
89
  packageRootsDirty = true;
57
90
  },
91
+ inspect,
58
92
  isAllowed(filePath) {
59
- if (isInjectedPackageFilePath(filePath))
60
- return true;
61
- if (!allowMatchers.some((matcher) => matcher(filePath)) && !isAllowedPackage(filePath)) {
62
- return false;
63
- }
64
- if (denyMatchers.length > 0 && denyMatchers.some((matcher) => matcher(filePath)))
65
- return false;
66
- return true;
93
+ return inspect(filePath).allowed;
67
94
  },
68
95
  };
69
96
  }
@@ -87,7 +114,7 @@ function validatePackageName(packageName, message) {
87
114
  }
88
115
  }
89
116
  function createPackageRootPaths(options) {
90
- let allowPackageRootPaths = new Set();
117
+ let allowPackageRootPaths = new Map();
91
118
  let allowQueue = [];
92
119
  let seenAllowedPackageRoots = new Set();
93
120
  let searchRoots = normalizePackageSearchRoots(options.searchRoots);
@@ -105,13 +132,13 @@ function createPackageRootPaths(options) {
105
132
  }
106
133
  }
107
134
  while (allowQueue.length > 0) {
108
- let { packageJsonPath } = allowQueue.shift();
135
+ let { packageJsonPath, packageName } = allowQueue.shift();
109
136
  let packageRootPath = normalizeFilePath(path.dirname(packageJsonPath));
110
137
  if (seenAllowedPackageRoots.has(packageRootPath))
111
138
  continue;
112
139
  seenAllowedPackageRoots.add(packageRootPath);
113
140
  let packageJson = readPackageJson(packageJsonPath);
114
- allowPackageRootPaths.add(packageRootPath);
141
+ allowPackageRootPaths.set(packageRootPath, packageName);
115
142
  for (let dependencyName of Object.keys(packageJson.dependencies ?? {})) {
116
143
  validatePackageName(dependencyName, `Dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`);
117
144
  let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath);
@@ -138,7 +165,7 @@ function createPackageRootPaths(options) {
138
165
  }
139
166
  function createPackageRootPathTrie(packageRootPaths) {
140
167
  let rootNode = createPackageRootPathTrieNode();
141
- for (let packageRootPath of packageRootPaths) {
168
+ for (let [packageRootPath, packageName] of packageRootPaths) {
142
169
  let node = rootNode;
143
170
  for (let segment of getFilePathSegments(packageRootPath)) {
144
171
  let childNode = node.children.get(segment);
@@ -148,29 +175,28 @@ function createPackageRootPathTrie(packageRootPaths) {
148
175
  }
149
176
  node = childNode;
150
177
  }
151
- node.packageRoot = true;
178
+ node.packageName = packageName;
152
179
  }
153
180
  return rootNode;
154
181
  }
155
182
  function createPackageRootPathTrieNode() {
156
183
  return {
157
184
  children: new Map(),
158
- packageRoot: false,
159
185
  };
160
186
  }
161
- function isPathInPackageRootPathTrie(filePath, trie) {
187
+ function getPackageNameFromRootPathTrie(filePath, trie) {
162
188
  let node = trie;
163
- if (node.packageRoot)
164
- return true;
189
+ if (node.packageName !== undefined)
190
+ return node.packageName;
165
191
  for (let segment of getFilePathSegments(filePath)) {
166
192
  let childNode = node.children.get(segment);
167
193
  if (!childNode)
168
- return false;
169
- if (childNode.packageRoot)
170
- return true;
194
+ return undefined;
195
+ if (childNode.packageName !== undefined)
196
+ return childNode.packageName;
171
197
  node = childNode;
172
198
  }
173
- return false;
199
+ return undefined;
174
200
  }
175
201
  function getFilePathSegments(filePath) {
176
202
  return normalizeFilePath(filePath).split('/');
@@ -3,6 +3,7 @@ import type { HmrPayload } from './hmr.ts';
3
3
  import type { ModuleLoader } from './loaders.ts';
4
4
  import type { ScriptHmrUpdate } from './scripts/compiler.ts';
5
5
  import type { AssetTarget } from './target.ts';
6
+ import { type AssetDetails } from './inspection.ts';
6
7
  import type { ChokidarWatcher } from './watch.ts';
7
8
  interface AssetServerWatchOptions {
8
9
  /**
@@ -135,8 +136,14 @@ interface AssetServerScriptOptions {
135
136
  export interface AssetServerOptions<transforms extends AssetRequestTransformMap = {}> {
136
137
  /** Public mount path for this asset server, e.g. `'/assets'`. */
137
138
  basePath: string;
138
- /** File patterns keyed by public URL patterns. */
139
- fileMap: Readonly<Record<string, string>>;
139
+ /**
140
+ * Directories to mount at public URL paths.
141
+ *
142
+ * Each key is a public URL path and its value is a directory relative to `rootDir`. Defaults to
143
+ * `{ app: 'app', npm: 'node_modules' }`. Public paths must not contain query strings, fragments,
144
+ * or encoded dot segments.
145
+ */
146
+ mounts?: Readonly<Record<string, string>>;
140
147
  /**
141
148
  * Root directory used to resolve relative file paths. Defaults to `process.cwd()`.
142
149
  */
@@ -147,7 +154,7 @@ export interface AssetServerOptions<transforms extends AssetRequestTransformMap
147
154
  allowFiles: readonly string[];
148
155
  /**
149
156
  * Exact package names whose files are allowed to be served. Dependencies and installed optional
150
- * dependencies are allowed automatically. Package files must still match `fileMap`.
157
+ * dependencies are allowed automatically. Package files must still be within a configured mount.
151
158
  */
152
159
  allowPackages?: readonly string[];
153
160
  /**
@@ -237,6 +244,16 @@ export interface AssetServer<transforms extends AssetRequestTransformMap = {}> {
237
244
  * Returns preload URLs for one or more served asset files, ordered shallowest-first.
238
245
  */
239
246
  getPreloads(filePath: string | readonly string[]): Promise<string[]>;
247
+ /**
248
+ * Returns diagnostic details about one public asset URL or file path, including the matched mount
249
+ * roots, access rules, file type, and browser-reachability status.
250
+ */
251
+ getAssetDetails(input: string): Promise<AssetDetails>;
252
+ /**
253
+ * Returns every file currently reachable through this asset server, sorted by public URL and
254
+ * then absolute file path.
255
+ */
256
+ getAssets(): Promise<AssetDetails[]>;
240
257
  /**
241
258
  * Closes this server's filesystem watcher and browser HMR channel.
242
259
  *
@@ -250,7 +267,7 @@ export declare function getInternalWatchTargets<transforms extends AssetRequestT
250
267
  * Create an asset server instance
251
268
  *
252
269
  * Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
253
- * source-based URL fingerprinting, caching, and configurable file mapping.
270
+ * source-based URL fingerprinting, caching, and configurable directory mounts.
254
271
  *
255
272
  * @param options Server configuration
256
273
  * @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
@@ -259,9 +276,6 @@ export declare function getInternalWatchTargets<transforms extends AssetRequestT
259
276
  * ```ts
260
277
  * let assetServer = createAssetServer({
261
278
  * basePath: '/assets',
262
- * fileMap: {
263
- * '/app/*path': 'app/*path',
264
- * },
265
279
  * allowFiles: ['app/routes.ts', 'app/**\/public/**'],
266
280
  * allowPackages: ['remix'],
267
281
  * denyFiles: ['app/**\/*.test.*'],