@remix-run/assets 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +319 -2
- package/dist/assets.d.ts +3 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +10 -0
- package/dist/lib/access.d.ts.map +1 -0
- package/dist/lib/access.js +14 -0
- package/dist/lib/asset-server.d.ts +137 -0
- package/dist/lib/asset-server.d.ts.map +1 -0
- package/dist/lib/asset-server.js +242 -0
- package/dist/lib/compilation-error.d.ts +33 -0
- package/dist/lib/compilation-error.d.ts.map +1 -0
- package/dist/lib/compilation-error.js +32 -0
- package/dist/lib/file-matcher.d.ts +6 -0
- package/dist/lib/file-matcher.d.ts.map +1 -0
- package/dist/lib/file-matcher.js +43 -0
- package/dist/lib/fingerprint.d.ts +12 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +49 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +50 -0
- package/dist/lib/routes.d.ts +13 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +94 -0
- package/dist/lib/scripts/cjs-check.d.ts +3 -0
- package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
- package/dist/lib/scripts/cjs-check.js +398 -0
- package/dist/lib/scripts/compiler.d.ts +62 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -0
- package/dist/lib/scripts/compiler.js +435 -0
- package/dist/lib/scripts/emit.d.ts +25 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -0
- package/dist/lib/scripts/emit.js +63 -0
- package/dist/lib/scripts/resolve.d.ts +60 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -0
- package/dist/lib/scripts/resolve.js +230 -0
- package/dist/lib/scripts/store.d.ts +40 -0
- package/dist/lib/scripts/store.d.ts.map +1 -0
- package/dist/lib/scripts/store.js +228 -0
- package/dist/lib/scripts/transform.d.ts +62 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -0
- package/dist/lib/scripts/transform.js +362 -0
- package/dist/lib/source-maps.d.ts +4 -0
- package/dist/lib/source-maps.d.ts.map +1 -0
- package/dist/lib/source-maps.js +56 -0
- package/dist/lib/watch.d.ts +22 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +96 -0
- package/package.json +50 -12
- package/src/assets.ts +2 -0
- package/src/lib/access.ts +24 -0
- package/src/lib/asset-server.ts +415 -0
- package/src/lib/compilation-error.ts +61 -0
- package/src/lib/file-matcher.ts +62 -0
- package/src/lib/fingerprint.ts +65 -0
- package/src/lib/paths.ts +66 -0
- package/src/lib/routes.ts +164 -0
- package/src/lib/scripts/cjs-check.ts +476 -0
- package/src/lib/scripts/compiler.ts +622 -0
- package/src/lib/scripts/emit.ts +122 -0
- package/src/lib/scripts/resolve.ts +422 -0
- package/src/lib/scripts/store.ts +327 -0
- package/src/lib/scripts/transform.ts +594 -0
- package/src/lib/source-maps.ts +68 -0
- package/src/lib/watch.ts +136 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { isAssetServerCompilationError } from "./compilation-error.js";
|
|
4
|
+
import { createAccessPolicy } from "./access.js";
|
|
5
|
+
import { createModuleCompiler, createResponseForModule } from "./scripts/compiler.js";
|
|
6
|
+
import { normalizeFilePath } from "./paths.js";
|
|
7
|
+
import { compileRoutes } from "./routes.js";
|
|
8
|
+
import { createAssetServerWatcher } from "./watch.js";
|
|
9
|
+
const scriptTargets = [
|
|
10
|
+
'es2015',
|
|
11
|
+
'es2016',
|
|
12
|
+
'es2017',
|
|
13
|
+
'es2018',
|
|
14
|
+
'es2019',
|
|
15
|
+
'es2020',
|
|
16
|
+
'es2021',
|
|
17
|
+
'es2022',
|
|
18
|
+
'es2023',
|
|
19
|
+
'es2024',
|
|
20
|
+
'es2025',
|
|
21
|
+
'es2026',
|
|
22
|
+
'esnext',
|
|
23
|
+
];
|
|
24
|
+
const scriptTargetSet = new Set(scriptTargets);
|
|
25
|
+
const chokidarWatcherByAssetServer = new WeakMap();
|
|
26
|
+
const watcherByAssetServer = new WeakMap();
|
|
27
|
+
export function getInternalChokidarWatcher(assetServer) {
|
|
28
|
+
return chokidarWatcherByAssetServer.get(assetServer);
|
|
29
|
+
}
|
|
30
|
+
export function getInternalWatchTargets(assetServer) {
|
|
31
|
+
return watcherByAssetServer.get(assetServer)?.getWatchedTargets() ?? [];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create an asset server instance
|
|
35
|
+
*
|
|
36
|
+
* Compiles TypeScript/JavaScript modules on demand with optional source-based URL
|
|
37
|
+
* fingerprinting, caching, and configurable file mapping.
|
|
38
|
+
*
|
|
39
|
+
* @param options Server configuration
|
|
40
|
+
* @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* let assetServer = createAssetServer({
|
|
45
|
+
* fileMap: {
|
|
46
|
+
* '/assets/app/*path': 'app/*path',
|
|
47
|
+
* },
|
|
48
|
+
* allow: ['app/**'],
|
|
49
|
+
* })
|
|
50
|
+
*
|
|
51
|
+
* route('/assets/*path', ({ request }) => assetServer.fetch(request))
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createAssetServer(options) {
|
|
55
|
+
let resolvedOptions = resolveAssetServerOptions(options);
|
|
56
|
+
let accessPolicy = createAccessPolicy({
|
|
57
|
+
allow: resolvedOptions.allow,
|
|
58
|
+
deny: resolvedOptions.deny,
|
|
59
|
+
rootDir: resolvedOptions.rootDir,
|
|
60
|
+
});
|
|
61
|
+
let watcher = null;
|
|
62
|
+
let chokidarWatcher = null;
|
|
63
|
+
let moduleCompiler = createModuleCompiler({
|
|
64
|
+
buildId: resolvedOptions.buildId,
|
|
65
|
+
define: resolvedOptions.define,
|
|
66
|
+
external: resolvedOptions.external,
|
|
67
|
+
fingerprintModules: resolvedOptions.fingerprintModules,
|
|
68
|
+
isAllowed: accessPolicy.isAllowed,
|
|
69
|
+
minify: resolvedOptions.minify,
|
|
70
|
+
onWatchDirectoriesChange: (delta) => {
|
|
71
|
+
if (!watcher)
|
|
72
|
+
return;
|
|
73
|
+
watcher.updateWatchedDirectories(delta);
|
|
74
|
+
},
|
|
75
|
+
rootDir: resolvedOptions.rootDir,
|
|
76
|
+
routes: resolvedOptions.routes,
|
|
77
|
+
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
78
|
+
sourceMaps: resolvedOptions.sourceMaps,
|
|
79
|
+
target: resolvedOptions.scriptsTarget,
|
|
80
|
+
watchIgnore: resolvedOptions.watchOptions?.ignore,
|
|
81
|
+
watchMode: resolvedOptions.watchOptions !== null,
|
|
82
|
+
});
|
|
83
|
+
if (resolvedOptions.watchOptions) {
|
|
84
|
+
watcher = createAssetServerWatcher({
|
|
85
|
+
...resolvedOptions.watchOptions,
|
|
86
|
+
onChokidarWatcherCreated(createdWatcher) {
|
|
87
|
+
chokidarWatcher = createdWatcher;
|
|
88
|
+
},
|
|
89
|
+
onFileEvent: handleWatchEvent,
|
|
90
|
+
rootDir: resolvedOptions.rootDir,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function responseForError(error) {
|
|
94
|
+
try {
|
|
95
|
+
return (await resolvedOptions.onError(error)) ?? internalServerError();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(`There was an error in the asset server error handler: ${error}`);
|
|
99
|
+
return internalServerError();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function handleWatchEvent(filePath, event) {
|
|
103
|
+
try {
|
|
104
|
+
let normalizedFilePath = normalizeFilePath(filePath);
|
|
105
|
+
await moduleCompiler.handleFileEvent(normalizedFilePath, event);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error(`There was an error invalidating the asset server cache: ${error}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let assetServer = {
|
|
112
|
+
async fetch(request) {
|
|
113
|
+
if (request.method !== 'GET' && request.method !== 'HEAD')
|
|
114
|
+
return null;
|
|
115
|
+
let parsedRequestPathname = moduleCompiler.parseRequestPathname(new URL(request.url).pathname);
|
|
116
|
+
if (!parsedRequestPathname)
|
|
117
|
+
return null;
|
|
118
|
+
try {
|
|
119
|
+
let ifNoneMatch = request.headers.get('If-None-Match');
|
|
120
|
+
let moduleResult = await moduleCompiler.getModule(parsedRequestPathname.filePath, {
|
|
121
|
+
ifNoneMatch,
|
|
122
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
123
|
+
requestedFingerprint: parsedRequestPathname.requestedFingerprint,
|
|
124
|
+
});
|
|
125
|
+
if (moduleResult.type === 'not-modified') {
|
|
126
|
+
return new Response(null, {
|
|
127
|
+
status: 304,
|
|
128
|
+
headers: { ETag: moduleResult.etag },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
let compiledModule = moduleResult.module;
|
|
132
|
+
if (parsedRequestPathname.requestedFingerprint !== null) {
|
|
133
|
+
if (compiledModule.fingerprint !== parsedRequestPathname.requestedFingerprint)
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return createResponseForModule(compiledModule, {
|
|
137
|
+
cacheControl: parsedRequestPathname.cacheControl,
|
|
138
|
+
ifNoneMatch,
|
|
139
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
140
|
+
method: request.method,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// A direct request can race with the filesystem or fail a deeper allow check while
|
|
145
|
+
// compiling imports. In this fetch context, both cases should fall through as "not
|
|
146
|
+
// handled here" so the outer router can continue to its own 404 behavior.
|
|
147
|
+
if (isAssetServerCompilationError(error) &&
|
|
148
|
+
(error.code === 'MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_ALLOWED')) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return responseForError(error);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
async getHref(filePath) {
|
|
155
|
+
return moduleCompiler.getHref(filePath);
|
|
156
|
+
},
|
|
157
|
+
async getPreloads(filePath) {
|
|
158
|
+
return moduleCompiler.getPreloadUrls(filePath);
|
|
159
|
+
},
|
|
160
|
+
async close() {
|
|
161
|
+
await watcher?.close();
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
if (chokidarWatcher) {
|
|
165
|
+
chokidarWatcherByAssetServer.set(assetServer, chokidarWatcher);
|
|
166
|
+
}
|
|
167
|
+
if (watcher) {
|
|
168
|
+
watcherByAssetServer.set(assetServer, watcher);
|
|
169
|
+
}
|
|
170
|
+
return assetServer;
|
|
171
|
+
}
|
|
172
|
+
function internalServerError() {
|
|
173
|
+
return new Response('Internal Server Error', {
|
|
174
|
+
status: 500,
|
|
175
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function defaultErrorHandler(error) {
|
|
179
|
+
console.error(error);
|
|
180
|
+
}
|
|
181
|
+
function resolveAssetServerOptions(options) {
|
|
182
|
+
let rootDir = normalizeFilePath(fs.realpathSync(path.resolve(options.rootDir ?? process.cwd())));
|
|
183
|
+
let scriptOptions = options.scripts ?? {};
|
|
184
|
+
let fingerprintOptions = normalizeFingerprintOptions({
|
|
185
|
+
fingerprint: options.fingerprint,
|
|
186
|
+
watch: options.watch,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
allow: options.allow,
|
|
190
|
+
buildId: fingerprintOptions.buildId,
|
|
191
|
+
define: scriptOptions.define,
|
|
192
|
+
deny: options.deny,
|
|
193
|
+
external: scriptOptions.external ?? [],
|
|
194
|
+
fingerprintModules: fingerprintOptions.enabled,
|
|
195
|
+
minify: scriptOptions.minify ?? false,
|
|
196
|
+
onError: options.onError ?? defaultErrorHandler,
|
|
197
|
+
rootDir,
|
|
198
|
+
routes: compileRoutes({
|
|
199
|
+
fileMap: options.fileMap,
|
|
200
|
+
rootDir,
|
|
201
|
+
}),
|
|
202
|
+
sourceMapSourcePaths: scriptOptions.sourceMapSourcePaths ?? 'url',
|
|
203
|
+
sourceMaps: scriptOptions.sourceMaps,
|
|
204
|
+
scriptsTarget: normalizeTarget(scriptOptions.target),
|
|
205
|
+
watchOptions: normalizeWatchOptions(options.watch),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function normalizeTarget(target) {
|
|
209
|
+
if (target == null)
|
|
210
|
+
return undefined;
|
|
211
|
+
if (typeof target !== 'string' || !scriptTargetSet.has(target)) {
|
|
212
|
+
throw new TypeError(`Expected target to be one of ${scriptTargets.map((value) => `"${value}"`).join(', ')}. Received "${target}".`);
|
|
213
|
+
}
|
|
214
|
+
return target;
|
|
215
|
+
}
|
|
216
|
+
function normalizeFingerprintOptions(options) {
|
|
217
|
+
if (!options.fingerprint) {
|
|
218
|
+
return {
|
|
219
|
+
enabled: false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (typeof options.fingerprint.buildId !== 'string') {
|
|
223
|
+
throw new TypeError('fingerprint.buildId must be a string');
|
|
224
|
+
}
|
|
225
|
+
if (options.fingerprint.buildId.length === 0) {
|
|
226
|
+
throw new TypeError('fingerprint.buildId must be a non-empty string');
|
|
227
|
+
}
|
|
228
|
+
if (options.watch !== false) {
|
|
229
|
+
throw new TypeError('fingerprint cannot be used with watch mode');
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
enabled: true,
|
|
233
|
+
buildId: options.fingerprint.buildId,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function normalizeWatchOptions(options) {
|
|
237
|
+
if (options === false)
|
|
238
|
+
return null;
|
|
239
|
+
if (options == null || options === true)
|
|
240
|
+
return {};
|
|
241
|
+
return options;
|
|
242
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type AssetServerCompilationErrorCode = 'MODULE_NOT_FOUND' | 'MODULE_NOT_ALLOWED' | 'MODULE_OUTSIDE_FILE_MAP' | 'MODULE_COMMONJS_NOT_SUPPORTED' | 'MODULE_TRANSFORM_FAILED' | 'MODULE_EMIT_FAILED' | 'IMPORT_RESOLUTION_FAILED' | 'IMPORT_NOT_SUPPORTED' | 'IMPORT_NOT_ALLOWED' | 'IMPORT_OUTSIDE_FILE_MAP';
|
|
2
|
+
/**
|
|
3
|
+
* Internal error used by the request-time module compilation pipeline.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AssetServerCompilationError extends Error {
|
|
6
|
+
code: AssetServerCompilationErrorCode;
|
|
7
|
+
constructor(message: string, options: {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
code: AssetServerCompilationErrorCode;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when a value is an `AssetServerCompilationError`.
|
|
14
|
+
*
|
|
15
|
+
* @param error Value thrown by the compilation pipeline.
|
|
16
|
+
* @returns Whether the value is an `AssetServerCompilationError`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isAssetServerCompilationError(error: unknown): error is AssetServerCompilationError;
|
|
19
|
+
/**
|
|
20
|
+
* Creates an `AssetServerCompilationError` with a stable internal code.
|
|
21
|
+
*
|
|
22
|
+
* @param message Human-readable error message.
|
|
23
|
+
* @param options Structured internal error details.
|
|
24
|
+
* @param options.cause Original error cause, when available.
|
|
25
|
+
* @param options.code Stable internal compilation error code.
|
|
26
|
+
* @returns A `AssetServerCompilationError`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createAssetServerCompilationError(message: string, options: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
code: AssetServerCompilationErrorCode;
|
|
31
|
+
}): AssetServerCompilationError;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=compilation-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compilation-error.d.ts","sourceRoot":"","sources":["../../src/lib/compilation-error.ts"],"names":[],"mappings":"AAAA,KAAK,+BAA+B,GAChC,kBAAkB,GAClB,oBAAoB,GACpB,yBAAyB,GACzB,+BAA+B,GAC/B,yBAAyB,GACzB,oBAAoB,GACpB,0BAA0B,GAC1B,sBAAsB,GACtB,oBAAoB,GACpB,yBAAyB,CAAA;AAE7B;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,IAAI,EAAE,+BAA+B,CAAA;IAErC,YACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,IAAI,EAAE,+BAA+B,CAAA;KACtC,EAKF;CACF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,2BAA2B,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,+BAA+B,CAAA;CACtC,GACA,2BAA2B,CAE7B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal error used by the request-time module compilation pipeline.
|
|
3
|
+
*/
|
|
4
|
+
export class AssetServerCompilationError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
8
|
+
this.name = 'AssetServerCompilationError';
|
|
9
|
+
this.code = options.code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when a value is an `AssetServerCompilationError`.
|
|
14
|
+
*
|
|
15
|
+
* @param error Value thrown by the compilation pipeline.
|
|
16
|
+
* @returns Whether the value is an `AssetServerCompilationError`.
|
|
17
|
+
*/
|
|
18
|
+
export function isAssetServerCompilationError(error) {
|
|
19
|
+
return error instanceof AssetServerCompilationError;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates an `AssetServerCompilationError` with a stable internal code.
|
|
23
|
+
*
|
|
24
|
+
* @param message Human-readable error message.
|
|
25
|
+
* @param options Structured internal error details.
|
|
26
|
+
* @param options.cause Original error cause, when available.
|
|
27
|
+
* @param options.code Stable internal compilation error code.
|
|
28
|
+
* @returns A `AssetServerCompilationError`.
|
|
29
|
+
*/
|
|
30
|
+
export function createAssetServerCompilationError(message, options) {
|
|
31
|
+
return new AssetServerCompilationError(message, options);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-matcher.d.ts","sourceRoot":"","sources":["../../src/lib/file-matcher.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAA;AAEvD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;CAClB,GACL,WAAW,CA0Bb"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { normalizeFilePath, resolveFilePath } from "./paths.js";
|
|
4
|
+
export function createFileMatcher(pattern, rootDir, options = {}) {
|
|
5
|
+
let resolvedPatternPath = resolveFilePath(rootDir, pattern);
|
|
6
|
+
let allowDirectories = options.allowDirectories ?? true;
|
|
7
|
+
let allowMissing = options.allowMissing ?? true;
|
|
8
|
+
if (!containsGlobSyntax(pattern)) {
|
|
9
|
+
try {
|
|
10
|
+
resolvedPatternPath = normalizeFilePath(fs.realpathSync(resolvedPatternPath));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (!allowMissing || !isPathNotFoundError(error))
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
if (allowDirectories) {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.statSync(resolveFilePath(rootDir, pattern)).isDirectory()) {
|
|
19
|
+
return (filePath) => isSameOrDescendantPath(filePath, resolvedPatternPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!isPathNotFoundError(error))
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return (filePath) => filePath === resolvedPatternPath;
|
|
28
|
+
}
|
|
29
|
+
return (filePath) => path.posix.matchesGlob(filePath, resolvedPatternPath);
|
|
30
|
+
}
|
|
31
|
+
function isSameOrDescendantPath(filePath, directoryPath) {
|
|
32
|
+
let normalizedDirectoryPath = directoryPath.replace(/\/+$/, '');
|
|
33
|
+
return filePath === normalizedDirectoryPath || filePath.startsWith(`${normalizedDirectoryPath}/`);
|
|
34
|
+
}
|
|
35
|
+
function containsGlobSyntax(pattern) {
|
|
36
|
+
return /[*?[\]{}()!+@]/.test(pattern);
|
|
37
|
+
}
|
|
38
|
+
function isPathNotFoundError(error) {
|
|
39
|
+
return (error instanceof Error &&
|
|
40
|
+
'code' in error &&
|
|
41
|
+
(error.code === 'ENOENT' ||
|
|
42
|
+
error.code === 'ENOTDIR'));
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function hashContent(content: string): Promise<string>;
|
|
2
|
+
export declare function generateFingerprint(options: {
|
|
3
|
+
buildId: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}): Promise<string>;
|
|
6
|
+
export declare function parseFingerprintSuffix(pathname: string): {
|
|
7
|
+
pathname: string;
|
|
8
|
+
requestedFingerprint: string | null;
|
|
9
|
+
};
|
|
10
|
+
export declare function formatFingerprintedPathname(pathname: string, fingerprint: string | null): string;
|
|
11
|
+
export declare function getFingerprintRequestCacheControl(requestedFingerprint: string | null): string;
|
|
12
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAElB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC,CAyBA;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAahG;AAED,wBAAgB,iCAAiC,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE7F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fingerprintedExtensionRE = /^(.+)\.@([A-Za-z0-9_-]+)(\.[^./]+)$/;
|
|
2
|
+
const fingerprintedBasenameRE = /^(.+)\.@([A-Za-z0-9_-]+)$/;
|
|
3
|
+
export async function hashContent(content) {
|
|
4
|
+
let encoder = new TextEncoder();
|
|
5
|
+
let data = encoder.encode(content);
|
|
6
|
+
let hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
7
|
+
return Buffer.from(hashBuffer).toString('base64url').slice(0, 6);
|
|
8
|
+
}
|
|
9
|
+
export async function generateFingerprint(options) {
|
|
10
|
+
return hashContent(JSON.stringify([options.content, options.buildId]));
|
|
11
|
+
}
|
|
12
|
+
export function parseFingerprintSuffix(pathname) {
|
|
13
|
+
let lastSlashIndex = pathname.lastIndexOf('/');
|
|
14
|
+
let directory = lastSlashIndex >= 0 ? pathname.slice(0, lastSlashIndex + 1) : '';
|
|
15
|
+
let basename = lastSlashIndex >= 0 ? pathname.slice(lastSlashIndex + 1) : pathname;
|
|
16
|
+
let extensionMatch = basename.match(fingerprintedExtensionRE);
|
|
17
|
+
if (extensionMatch) {
|
|
18
|
+
return {
|
|
19
|
+
pathname: `${directory}${extensionMatch[1]}${extensionMatch[3]}`,
|
|
20
|
+
requestedFingerprint: extensionMatch[2],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
let basenameMatch = basename.match(fingerprintedBasenameRE);
|
|
24
|
+
if (basenameMatch) {
|
|
25
|
+
return {
|
|
26
|
+
pathname: `${directory}${basenameMatch[1]}`,
|
|
27
|
+
requestedFingerprint: basenameMatch[2],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
pathname,
|
|
32
|
+
requestedFingerprint: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function formatFingerprintedPathname(pathname, fingerprint) {
|
|
36
|
+
if (fingerprint === null)
|
|
37
|
+
return pathname;
|
|
38
|
+
let lastSlashIndex = pathname.lastIndexOf('/');
|
|
39
|
+
let directory = lastSlashIndex >= 0 ? pathname.slice(0, lastSlashIndex + 1) : '';
|
|
40
|
+
let basename = lastSlashIndex >= 0 ? pathname.slice(lastSlashIndex + 1) : pathname;
|
|
41
|
+
let lastDotIndex = basename.lastIndexOf('.');
|
|
42
|
+
if (lastDotIndex <= 0) {
|
|
43
|
+
return `${pathname}.@${fingerprint}`;
|
|
44
|
+
}
|
|
45
|
+
return `${directory}${basename.slice(0, lastDotIndex)}.@${fingerprint}${basename.slice(lastDotIndex)}`;
|
|
46
|
+
}
|
|
47
|
+
export function getFingerprintRequestCacheControl(requestedFingerprint) {
|
|
48
|
+
return requestedFingerprint === null ? 'no-cache' : 'public, max-age=31536000, immutable';
|
|
49
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function normalizeWindowsPath(filePath: string): string;
|
|
2
|
+
export declare function normalizePathname(pathname: string): string;
|
|
3
|
+
export declare function isAbsoluteFilePath(filePath: string): boolean;
|
|
4
|
+
export declare function normalizeFilePath(filePath: string): string;
|
|
5
|
+
export declare function resolveFilePath(rootDir: string, filePath: string): string;
|
|
6
|
+
export declare function getFilePathDirectory(filePath: string): string;
|
|
7
|
+
export declare function getFilePathBaseName(filePath: string): string;
|
|
8
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB1D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMzE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
const windowsDriveLetterRE = /^[A-Za-z]:\//;
|
|
3
|
+
const uncPrefixRE = /^\/\/[^/]+\/[^/]+/;
|
|
4
|
+
export function normalizeWindowsPath(filePath) {
|
|
5
|
+
return filePath
|
|
6
|
+
.replace(/\\/g, '/')
|
|
7
|
+
.replace(windowsDriveLetterRE, (prefix) => `${prefix[0].toUpperCase()}${prefix.slice(1)}`);
|
|
8
|
+
}
|
|
9
|
+
export function normalizePathname(pathname) {
|
|
10
|
+
let normalized = path.posix.normalize(normalizeWindowsPath(pathname));
|
|
11
|
+
if (!normalized.startsWith('/')) {
|
|
12
|
+
normalized = `/${normalized}`;
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
export function isAbsoluteFilePath(filePath) {
|
|
17
|
+
let normalized = normalizeWindowsPath(filePath);
|
|
18
|
+
return normalized.startsWith('/') || windowsDriveLetterRE.test(normalized);
|
|
19
|
+
}
|
|
20
|
+
export function normalizeFilePath(filePath) {
|
|
21
|
+
let normalized = normalizeWindowsPath(filePath);
|
|
22
|
+
let uncRoot = getUncRoot(normalized);
|
|
23
|
+
if (uncRoot) {
|
|
24
|
+
let remainder = normalized.slice(uncRoot.length);
|
|
25
|
+
let normalizedRemainder = path.posix.normalize(remainder || '/');
|
|
26
|
+
return `${uncRoot}${normalizedRemainder === '/' ? '' : normalizedRemainder}`;
|
|
27
|
+
}
|
|
28
|
+
if (windowsDriveLetterRE.test(normalized)) {
|
|
29
|
+
return path.posix.normalize(normalized);
|
|
30
|
+
}
|
|
31
|
+
if (normalized.startsWith('/')) {
|
|
32
|
+
return path.posix.normalize(normalized);
|
|
33
|
+
}
|
|
34
|
+
return path.posix.normalize(normalizeWindowsPath(path.resolve(normalized)));
|
|
35
|
+
}
|
|
36
|
+
export function resolveFilePath(rootDir, filePath) {
|
|
37
|
+
if (isAbsoluteFilePath(filePath)) {
|
|
38
|
+
return normalizeFilePath(filePath);
|
|
39
|
+
}
|
|
40
|
+
return normalizeFilePath(`${rootDir.replace(/\/+$/, '')}/${normalizeWindowsPath(filePath)}`);
|
|
41
|
+
}
|
|
42
|
+
export function getFilePathDirectory(filePath) {
|
|
43
|
+
return path.posix.dirname(normalizeWindowsPath(filePath));
|
|
44
|
+
}
|
|
45
|
+
export function getFilePathBaseName(filePath) {
|
|
46
|
+
return path.posix.basename(normalizeWindowsPath(filePath));
|
|
47
|
+
}
|
|
48
|
+
function getUncRoot(filePath) {
|
|
49
|
+
return filePath.startsWith('//') ? (filePath.match(uncPrefixRE)?.[0] ?? null) : null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AssetRouteDefinition {
|
|
2
|
+
urlPattern: string;
|
|
3
|
+
filePattern: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CompiledRoutes {
|
|
6
|
+
resolveUrlPathname(pathname: string): string | null;
|
|
7
|
+
toUrlPathname(filePath: string): string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function compileRoutes(options: {
|
|
10
|
+
fileMap: Readonly<Record<string, string>>;
|
|
11
|
+
rootDir: string;
|
|
12
|
+
}): CompiledRoutes;
|
|
13
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/lib/routes.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,MAAM,WAAW,cAAc;IAC7B,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACnD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC/C;AAYD,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,cAAc,CA0CjB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { RoutePattern } from '@remix-run/route-pattern';
|
|
3
|
+
import { isAbsoluteFilePath, normalizeFilePath, normalizePathname, resolveFilePath, } from "./paths.js";
|
|
4
|
+
function normalizeFilePattern(pattern) {
|
|
5
|
+
if (isAbsoluteFilePath(pattern)) {
|
|
6
|
+
throw new Error(`File route patterns must be relative to the asset server root.\nPattern: ${pattern}`);
|
|
7
|
+
}
|
|
8
|
+
return normalizePathname(pattern);
|
|
9
|
+
}
|
|
10
|
+
export function compileRoutes(options) {
|
|
11
|
+
if (Object.keys(options.fileMap).length === 0) {
|
|
12
|
+
throw new Error('createAssetServer() requires at least one configured fileMap entry.');
|
|
13
|
+
}
|
|
14
|
+
let compiledRoutes = Object.entries(options.fileMap).map(([urlPattern, filePattern]) => compileRoute({
|
|
15
|
+
urlPattern,
|
|
16
|
+
filePattern,
|
|
17
|
+
}, { rootDir: options.rootDir }));
|
|
18
|
+
return {
|
|
19
|
+
resolveUrlPathname(pathname) {
|
|
20
|
+
let normalizedPathname = normalizePathname(pathname);
|
|
21
|
+
for (let route of compiledRoutes) {
|
|
22
|
+
let match = route.urlPattern.match(`http://remix.run${normalizedPathname}`);
|
|
23
|
+
if (!match)
|
|
24
|
+
continue;
|
|
25
|
+
let relativeFilePath = route.filePattern.href(match.params).replace(/^\/+/, '');
|
|
26
|
+
return resolveFilePath(route.rootDir, relativeFilePath);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
},
|
|
30
|
+
toUrlPathname(filePath) {
|
|
31
|
+
let normalizedFilePath = normalizeFilePath(filePath);
|
|
32
|
+
for (let route of compiledRoutes) {
|
|
33
|
+
let relativeFilePath = getRelativeFilePath(normalizedFilePath, route.rootDir);
|
|
34
|
+
if (relativeFilePath === null)
|
|
35
|
+
continue;
|
|
36
|
+
let match = route.filePattern.ast.pathname.match(relativeFilePath);
|
|
37
|
+
if (!match)
|
|
38
|
+
continue;
|
|
39
|
+
return normalizePathname(route.urlPattern.href(getPathnameParams(route.filePattern, match)));
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function compileRoute(route, options) {
|
|
46
|
+
let urlPatternSource = normalizePathname(route.urlPattern);
|
|
47
|
+
let filePatternSource = normalizeFilePattern(route.filePattern);
|
|
48
|
+
let urlPattern = new RoutePattern(urlPatternSource);
|
|
49
|
+
let filePattern = new RoutePattern(filePatternSource);
|
|
50
|
+
validateNoUnnamedWildcards(urlPattern, 'URL');
|
|
51
|
+
validateNoUnnamedWildcards(filePattern, 'File');
|
|
52
|
+
validateRoutePatterns(urlPattern, filePattern);
|
|
53
|
+
return {
|
|
54
|
+
rootDir: normalizeFilePath(options.rootDir).replace(/\/+$/, ''),
|
|
55
|
+
urlPattern,
|
|
56
|
+
filePattern,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getRelativeFilePath(filePath, rootDir) {
|
|
60
|
+
if (filePath[1] === ':' && rootDir[1] === ':' && filePath[0] !== rootDir[0])
|
|
61
|
+
return null;
|
|
62
|
+
return path.posix.relative(rootDir, filePath);
|
|
63
|
+
}
|
|
64
|
+
function getPathnameParams(pattern, match) {
|
|
65
|
+
let params = {};
|
|
66
|
+
for (let param of pattern.ast.pathname.params) {
|
|
67
|
+
if (param.name === '*')
|
|
68
|
+
continue;
|
|
69
|
+
params[param.name] = undefined;
|
|
70
|
+
}
|
|
71
|
+
for (let param of match) {
|
|
72
|
+
if (param.name === '*')
|
|
73
|
+
continue;
|
|
74
|
+
params[param.name] = param.value;
|
|
75
|
+
}
|
|
76
|
+
return params;
|
|
77
|
+
}
|
|
78
|
+
function validateRoutePatterns(urlPattern, filePattern) {
|
|
79
|
+
let urlParams = urlPattern.ast.pathname.params.map((param) => `${param.type}:${param.name}`);
|
|
80
|
+
let fileParams = filePattern.ast.pathname.params.map((param) => `${param.type}:${param.name}`);
|
|
81
|
+
if (urlParams.length !== fileParams.length) {
|
|
82
|
+
throw new Error(`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`);
|
|
83
|
+
}
|
|
84
|
+
for (let i = 0; i < urlParams.length; i++) {
|
|
85
|
+
if (urlParams[i] !== fileParams[i]) {
|
|
86
|
+
throw new Error(`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function validateNoUnnamedWildcards(pattern, label) {
|
|
91
|
+
if (pattern.ast.pathname.params.some((param) => param.type === '*' && param.name === '*')) {
|
|
92
|
+
throw new Error(`${label} route patterns must use named wildcards for reversible mapping.\nPattern: ${pattern}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cjs-check.d.ts","sourceRoot":"","sources":["../../../src/lib/scripts/cjs-check.ts"],"names":[],"mappings":"AAoBA,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEvE;AAGD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAalD"}
|