spooder 5.0.2 → 5.0.3
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 +114 -117
- package/package.json +1 -1
- package/src/api.ts +51 -41
package/README.md
CHANGED
|
@@ -525,8 +525,8 @@ pipe.once(event: string, callback: (data: object) => void | Promise<void>): void
|
|
|
525
525
|
pipe.off(event: string): void;
|
|
526
526
|
|
|
527
527
|
// templates
|
|
528
|
-
|
|
529
|
-
|
|
528
|
+
Replacements = Record<string, string | Array<string> | object | object[]> | ReplacerFn | AsyncReplaceFn;
|
|
529
|
+
parse_template(template: string, replacements: Replacements, drop_missing?: boolean): Promise<string>;
|
|
530
530
|
get_git_hashes(length: number): Promise<Record<string, string>>;
|
|
531
531
|
|
|
532
532
|
// database interface
|
|
@@ -1197,7 +1197,7 @@ server.websocket('/path/to/websocket', {
|
|
|
1197
1197
|
For simpler projects, the scaffolding can often look the same, potentially something similar to below.
|
|
1198
1198
|
|
|
1199
1199
|
```ts
|
|
1200
|
-
import { http_serve, cache_http,
|
|
1200
|
+
import { http_serve, cache_http, parse_template, http_apply_range, get_git_hashes } from 'spooder';
|
|
1201
1201
|
import path from 'node:path';
|
|
1202
1202
|
|
|
1203
1203
|
const server = http_serve(80);
|
|
@@ -1209,7 +1209,7 @@ const cache = cache_http({
|
|
|
1209
1209
|
});
|
|
1210
1210
|
|
|
1211
1211
|
const base_file = await Bun.file('./html/base_template.html').text();
|
|
1212
|
-
const
|
|
1212
|
+
const git_hash_table = await get_git_hashes();
|
|
1213
1213
|
|
|
1214
1214
|
async function default_handler(status_code: number): Promise<Response> {
|
|
1215
1215
|
const error_text = HTTP_STATUS_CODE[status_code] as string;
|
|
@@ -1239,10 +1239,12 @@ server.dir('/static', './static', async (file_path, file, stat, request) => {
|
|
|
1239
1239
|
if (stat.isDirectory())
|
|
1240
1240
|
return HTTP_STATUS_CODE.Unauthorized_401;
|
|
1241
1241
|
|
|
1242
|
-
//
|
|
1242
|
+
// serve css/js files directly
|
|
1243
1243
|
const ext = path.extname(file_path);
|
|
1244
1244
|
if (ext === '.css' || ext === '.js') {
|
|
1245
|
-
const content = await parse_template(await file.text(),
|
|
1245
|
+
const content = await parse_template(await file.text(), {
|
|
1246
|
+
asset: (file) => git_hash_table[file]
|
|
1247
|
+
}, true);
|
|
1246
1248
|
|
|
1247
1249
|
return new Response(content, {
|
|
1248
1250
|
headers: {
|
|
@@ -1261,8 +1263,8 @@ function add_route(route: string, file: string, title: string) {
|
|
|
1261
1263
|
const template = await parse_template(base_file, {
|
|
1262
1264
|
title: title,
|
|
1263
1265
|
content: file_content,
|
|
1264
|
-
|
|
1265
|
-
},
|
|
1266
|
+
asset: (file) => git_hash_table[file]
|
|
1267
|
+
}, true);
|
|
1266
1268
|
|
|
1267
1269
|
return template;
|
|
1268
1270
|
});
|
|
@@ -1300,7 +1302,7 @@ server.bootstrap({
|
|
|
1300
1302
|
error_page: Bun.file('./html/error.html')
|
|
1301
1303
|
},
|
|
1302
1304
|
|
|
1303
|
-
|
|
1305
|
+
cache_bust: true,
|
|
1304
1306
|
|
|
1305
1307
|
static: {
|
|
1306
1308
|
directory: './static',
|
|
@@ -1399,37 +1401,30 @@ cache: {
|
|
|
1399
1401
|
}
|
|
1400
1402
|
```
|
|
1401
1403
|
|
|
1402
|
-
##### `
|
|
1403
|
-
When enabled, automatically generates git hash-based
|
|
1404
|
+
##### `cache_bust?: boolean`
|
|
1405
|
+
When enabled, automatically generates git hash-based cache busting for static assets using dynamic template resolvers.
|
|
1404
1406
|
|
|
1405
|
-
**Boolean usage (uses defaults):**
|
|
1406
1407
|
```ts
|
|
1407
|
-
|
|
1408
|
+
cache_bust: true
|
|
1408
1409
|
```
|
|
1409
1410
|
|
|
1410
|
-
|
|
1411
|
-
```ts
|
|
1412
|
-
hash_subs: {
|
|
1413
|
-
length: 7, // Hash length (default: 7)
|
|
1414
|
-
prefix: 'asset=', // Substitution prefix (default: 'hash=')
|
|
1415
|
-
format: '$file?v=$hash', // Custom format (default: just hash)
|
|
1416
|
-
hashes: { ... } // Pre-generated hash map from get_git_hashes (optional)
|
|
1417
|
-
}
|
|
1418
|
-
```
|
|
1411
|
+
With `cache_bust` enabled, you can use the `{{asset=filename}}` template function to generate cache-busted URLs:
|
|
1419
1412
|
|
|
1420
|
-
**
|
|
1413
|
+
**Usage:**
|
|
1421
1414
|
```ts
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
// Usage: <link href="/css/style.css?v={{hash=static/css/style.css}}">
|
|
1415
|
+
cache_bust: true
|
|
1416
|
+
// Creates dynamic asset resolver: {{asset=static/css/style.css}} -> "static/css/style.css?v=a1b2c3d"
|
|
1417
|
+
```
|
|
1426
1418
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1419
|
+
**Template usage:**
|
|
1420
|
+
```html
|
|
1421
|
+
<link href="{{asset=static/css/style.css}}">
|
|
1422
|
+
<script src="{{asset=static/js/app.js}}"></script>
|
|
1423
|
+
<img src="{{asset=static/images/logo.png}}">
|
|
1431
1424
|
```
|
|
1432
1425
|
|
|
1426
|
+
The `asset` function automatically appends the git hash as a query parameter for cache busting, or returns the original filename if no hash is available.
|
|
1427
|
+
|
|
1433
1428
|
##### `error?: object`
|
|
1434
1429
|
Optional error page configuration:
|
|
1435
1430
|
- `error_page`: Template for error pages (string or BunFile)
|
|
@@ -1458,7 +1453,25 @@ static: {
|
|
|
1458
1453
|
}
|
|
1459
1454
|
```
|
|
1460
1455
|
|
|
1461
|
-
Files with extensions in `sub_ext` will have template substitutions applied before serving.
|
|
1456
|
+
Files with extensions in `sub_ext` will have template substitutions applied before serving. This includes support for functions to generate dynamic content:
|
|
1457
|
+
|
|
1458
|
+
```ts
|
|
1459
|
+
// Dynamic CSS with function-based substitutions
|
|
1460
|
+
static: {
|
|
1461
|
+
route: '/assets',
|
|
1462
|
+
directory: './public',
|
|
1463
|
+
sub_ext: ['.css']
|
|
1464
|
+
},
|
|
1465
|
+
|
|
1466
|
+
global_subs: {
|
|
1467
|
+
theme_color: () => {
|
|
1468
|
+
const hour = new Date().getHours();
|
|
1469
|
+
return hour < 6 || hour > 18 ? '#2d3748' : '#4a5568';
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
This allows CSS files to use dynamic substitutions: `color: {{theme_color}};`
|
|
1462
1475
|
|
|
1463
1476
|
##### `global_subs?: Record<string, BootstrapSub>`
|
|
1464
1477
|
Optional global template substitutions available to all routes, error pages, and static files with `sub_ext`.
|
|
@@ -1467,10 +1480,28 @@ Optional global template substitutions available to all routes, error pages, and
|
|
|
1467
1480
|
global_subs: {
|
|
1468
1481
|
site_name: 'My Website',
|
|
1469
1482
|
version: '1.0.0',
|
|
1470
|
-
api_url: 'https://api.example.com'
|
|
1483
|
+
api_url: 'https://api.example.com',
|
|
1484
|
+
|
|
1485
|
+
// Function-based substitutions for dynamic content
|
|
1486
|
+
current_year: () => new Date().getFullYear().toString(),
|
|
1487
|
+
|
|
1488
|
+
build_time: async () => {
|
|
1489
|
+
// Example: fetch build timestamp from git
|
|
1490
|
+
const process = Bun.spawn(['git', 'log', '-1', '--format=%ct']);
|
|
1491
|
+
const output = await Bun.readableStreamToText(process.stdout);
|
|
1492
|
+
return new Date(parseInt(output.trim()) * 1000).toISOString();
|
|
1493
|
+
},
|
|
1494
|
+
|
|
1495
|
+
user_count: async () => {
|
|
1496
|
+
// Example: dynamic user count from database
|
|
1497
|
+
const count = await db.count('SELECT COUNT(*) as count FROM users');
|
|
1498
|
+
return count.toLocaleString();
|
|
1499
|
+
}
|
|
1471
1500
|
}
|
|
1472
1501
|
```
|
|
1473
1502
|
|
|
1503
|
+
Functions in `global_subs` and route-specific `subs` are called during template processing, allowing for dynamic content generation. Both synchronous and asynchronous functions are supported.
|
|
1504
|
+
|
|
1474
1505
|
#### Template Processing Order
|
|
1475
1506
|
|
|
1476
1507
|
1. Route content is loaded
|
|
@@ -1829,29 +1860,60 @@ await parse_template(template, replacements, true);
|
|
|
1829
1860
|
</html>
|
|
1830
1861
|
```
|
|
1831
1862
|
|
|
1832
|
-
|
|
1863
|
+
#### Custom Replacer Function
|
|
1864
|
+
|
|
1865
|
+
`parse_template` supports passing a function instead of a replacement object. This function will be called for each placeholder and the return value will be used as the replacement. Both synchronous and asynchronous functions are supported.
|
|
1833
1866
|
|
|
1834
1867
|
```ts
|
|
1835
|
-
const replacer = (
|
|
1836
|
-
|
|
1868
|
+
const replacer = (key: string) => {
|
|
1869
|
+
switch (key) {
|
|
1870
|
+
case 'timestamp': return Date.now().toString();
|
|
1871
|
+
case 'random': return Math.random().toString(36).substring(7);
|
|
1872
|
+
case 'greeting': return 'Hello, World!';
|
|
1873
|
+
default: return undefined;
|
|
1874
|
+
}
|
|
1837
1875
|
};
|
|
1838
1876
|
|
|
1839
|
-
await parse_template('
|
|
1877
|
+
await parse_template('Generated at {{timestamp}}: {{greeting}} (ID: {{random}})', replacer);
|
|
1878
|
+
// Result: "Generated at 1635789123456: Hello, World! (ID: x7k2p9m)"
|
|
1840
1879
|
```
|
|
1841
1880
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1881
|
+
Custom replacer functions are supported on a per-key basis, mixing with static string replacement.
|
|
1882
|
+
|
|
1883
|
+
```ts
|
|
1884
|
+
await parse_template('Hello {{foo}}, it is {{now}}', {
|
|
1885
|
+
foo: 'world',
|
|
1886
|
+
now: () => Date.now()
|
|
1887
|
+
});
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
#### Key/Value Based Substitutions
|
|
1891
|
+
|
|
1892
|
+
`parse_template` supports key/value based substitutions using the `{{key=value}}` syntax. When a function replacer is provided for the key, the value is passed as a parameter to the function.
|
|
1893
|
+
|
|
1894
|
+
```ts
|
|
1895
|
+
await parse_template('Color: {{hex=blue}}', {
|
|
1896
|
+
hex: (color) => {
|
|
1897
|
+
const colors = { blue: '#0000ff', red: '#ff0000', green: '#00ff00' };
|
|
1898
|
+
return colors[color] || color;
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
// Result: "Color: #0000ff"
|
|
1902
|
+
```
|
|
1903
|
+
|
|
1904
|
+
Global replacer functions also support the value parameter:
|
|
1905
|
+
|
|
1906
|
+
```ts
|
|
1907
|
+
await parse_template('Transform: {{upper=hello}} and {{lower=WORLD}}', (key, value) => {
|
|
1908
|
+
if (key === 'upper' && value) return value.toUpperCase();
|
|
1909
|
+
if (key === 'lower' && value) return value.toLowerCase();
|
|
1910
|
+
return 'unknown';
|
|
1911
|
+
});
|
|
1912
|
+
// Result: "Transform: HELLO and world"
|
|
1853
1913
|
```
|
|
1854
1914
|
|
|
1915
|
+
#### Conditional Rendering
|
|
1916
|
+
|
|
1855
1917
|
`parse_template` supports conditional rendering with the following syntax.
|
|
1856
1918
|
|
|
1857
1919
|
```html
|
|
@@ -1940,72 +2002,10 @@ await parse_template(..., {
|
|
|
1940
2002
|
</t-for>
|
|
1941
2003
|
```
|
|
1942
2004
|
|
|
1943
|
-
### 🔧 `generate_hash_subs(length: number, prefix: string, hashes?: Record<string, string>, format?: string): Promise<Record<string, string>>`
|
|
1944
|
-
|
|
1945
|
-
Generate a replacement table for mapping file paths to hashes in templates. This is useful for cache-busting static assets.
|
|
1946
|
-
|
|
1947
|
-
> [!IMPORTANT]
|
|
1948
|
-
> Internally `generate_hash_subs()` uses `git ls-tree -r HEAD`, so the working directory must be a git repository.
|
|
1949
|
-
|
|
1950
|
-
```ts
|
|
1951
|
-
let hash_sub_table = {};
|
|
1952
|
-
|
|
1953
|
-
generate_hash_subs().then(subs => hash_sub_table = subs).catch(caution);
|
|
1954
|
-
|
|
1955
|
-
server.route('/test', (req, url) => {
|
|
1956
|
-
return parse_template('Hello world {{hash=docs/project-logo.png}}', hash_sub_table);
|
|
1957
|
-
});
|
|
1958
|
-
```
|
|
1959
|
-
|
|
1960
|
-
```html
|
|
1961
|
-
Hello world 754d9ea
|
|
1962
|
-
```
|
|
1963
|
-
|
|
1964
|
-
> [!IMPORTANT]
|
|
1965
|
-
> Specify paths as they appear in git, relative to the repository root and with forward slashes (no leading slash).
|
|
1966
|
-
|
|
1967
|
-
By default hashes are truncated to `7` characters (a short hash), a custom length can be provided instead.
|
|
1968
|
-
|
|
1969
|
-
```ts
|
|
1970
|
-
generate_hash_subs(40).then(...);
|
|
1971
|
-
// d65c52a41a75db43e184d2268c6ea9f9741de63e
|
|
1972
|
-
```
|
|
1973
|
-
|
|
1974
|
-
> [!NOTE]
|
|
1975
|
-
> SHA-1 hashes are `40` characters. Git is transitioning to SHA-256, which are `64` characters. Short hashes of `7` are generally sufficient for cache-busting.
|
|
1976
|
-
|
|
1977
|
-
Use a different prefix other than `hash=` by passing it as the first parameter.
|
|
1978
|
-
|
|
1979
|
-
```ts
|
|
1980
|
-
generate_hash_subs(7, '$#').then(subs => hash_sub_table = subs).catch(caution);
|
|
1981
|
-
|
|
1982
|
-
server.route('/test', (req, url) => {
|
|
1983
|
-
return parse_template('Hello world {{$#docs/project-logo.png}}', hash_sub_table);
|
|
1984
|
-
});
|
|
1985
|
-
```
|
|
1986
|
-
|
|
1987
|
-
#### Custom Format Parameter
|
|
1988
|
-
|
|
1989
|
-
The optional `format` parameter allows you to customize how the substitution values are formatted. Use `$file` and `$hash` placeholders within the format string:
|
|
1990
|
-
|
|
1991
|
-
```ts
|
|
1992
|
-
// Asset-style substitutions - reduces verbosity
|
|
1993
|
-
const asset_subs = await generate_hash_subs(7, 'asset=', undefined, '$file?v=$hash');
|
|
1994
|
-
|
|
1995
|
-
// Usage in templates:
|
|
1996
|
-
// <link rel="stylesheet" href="{{asset=static/css/shared.css}}">
|
|
1997
|
-
// Resolves to: static/css/shared.css?v=a1b2c3d
|
|
1998
|
-
|
|
1999
|
-
// Custom format example:
|
|
2000
|
-
await generate_hash_subs(7, 'url=', undefined, 'https://assets.example.com/$file?hash=$hash');
|
|
2001
|
-
// Result: { 'url=app.js': 'https://assets.example.com/app.js?hash=a1b2c3d' }
|
|
2002
|
-
```
|
|
2003
|
-
|
|
2004
|
-
When `format` is omitted, the default behavior returns just the hash value (backward compatible).
|
|
2005
2005
|
|
|
2006
2006
|
### 🔧 ``get_git_hashes(length: number): Promise<Record<string, string>>``
|
|
2007
2007
|
|
|
2008
|
-
|
|
2008
|
+
Retrieve git hashes for all files in the repository. This is useful for implementing cache-busting functionality or creating file integrity checks.
|
|
2009
2009
|
|
|
2010
2010
|
> [!IMPORTANT]
|
|
2011
2011
|
> Internally `get_git_hashes()` uses `git ls-tree -r HEAD`, so the working directory must be a git repository.
|
|
@@ -2015,14 +2015,11 @@ const hashes = await get_git_hashes(7);
|
|
|
2015
2015
|
// { 'docs/project-logo.png': '754d9ea' }
|
|
2016
2016
|
```
|
|
2017
2017
|
|
|
2018
|
-
|
|
2018
|
+
You can specify the hash length (default is 7 characters for short hashes):
|
|
2019
2019
|
|
|
2020
2020
|
```ts
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
// hashes[0] -> { 'docs/project-logo.png': '754d9ea' }
|
|
2025
|
-
// subs[0] -> { 'hash=docs/project-logo.png': '754d9ea' }
|
|
2021
|
+
const full_hashes = await get_git_hashes(40);
|
|
2022
|
+
// { 'docs/project-logo.png': 'd65c52a41a75db43e184d2268c6ea9f9741de63e' }
|
|
2026
2023
|
```
|
|
2027
2024
|
|
|
2028
2025
|
<a id="api-database"></a>
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -397,9 +397,14 @@ export async function safe(target_fn: Callable) {
|
|
|
397
397
|
// endregion
|
|
398
398
|
|
|
399
399
|
// region templates
|
|
400
|
-
type ReplacerFn = (key: string) => string | Array<string> | undefined;
|
|
401
|
-
type AsyncReplaceFn = (key: string) => Promise<string | Array<string> | undefined>;
|
|
402
|
-
type
|
|
400
|
+
type ReplacerFn = (key: string, value?: string) => string | Array<string> | undefined;
|
|
401
|
+
type AsyncReplaceFn = (key: string, value?: string) => Promise<string | Array<string> | undefined>;
|
|
402
|
+
type ReplacementValueFn = () => string | Array<string> | undefined;
|
|
403
|
+
type AsyncReplacementValueFn = () => Promise<string | Array<string> | undefined>;
|
|
404
|
+
type ReplacementValueWithKeyFn = (value?: string) => string | Array<string> | undefined;
|
|
405
|
+
type AsyncReplacementValueWithKeyFn = (value?: string) => Promise<string | Array<string> | undefined>;
|
|
406
|
+
type ReplacementValue = string | Array<string> | object | object[] | ReplacementValueFn | AsyncReplacementValueFn | ReplacementValueWithKeyFn | AsyncReplacementValueWithKeyFn;
|
|
407
|
+
type Replacements = Record<string, ReplacementValue> | ReplacerFn | AsyncReplaceFn;
|
|
403
408
|
|
|
404
409
|
function get_nested_property(obj: any, path: string): any {
|
|
405
410
|
const keys = path.split('.');
|
|
@@ -435,11 +440,14 @@ export async function parse_template(template: string, replacements: Replacement
|
|
|
435
440
|
|
|
436
441
|
if (typeof replacements === 'function') {
|
|
437
442
|
scoped_replacements = async (key: string) => {
|
|
438
|
-
if (key === alias_name)
|
|
443
|
+
if (key === alias_name)
|
|
444
|
+
return loop_entry;
|
|
445
|
+
|
|
439
446
|
if (key.startsWith(alias_name + '.')) {
|
|
440
447
|
const prop_path = key.substring(alias_name.length + 1);
|
|
441
448
|
return get_nested_property(loop_entry, prop_path);
|
|
442
449
|
}
|
|
450
|
+
|
|
443
451
|
return await replacements(key);
|
|
444
452
|
};
|
|
445
453
|
} else {
|
|
@@ -467,6 +475,7 @@ export async function parse_template(template: string, replacements: Replacement
|
|
|
467
475
|
|
|
468
476
|
if (!drop_missing && !condition_value)
|
|
469
477
|
return match;
|
|
478
|
+
|
|
470
479
|
if (condition_value)
|
|
471
480
|
return await parse_template(if_content, replacements, drop_missing);
|
|
472
481
|
|
|
@@ -478,15 +487,31 @@ export async function parse_template(template: string, replacements: Replacement
|
|
|
478
487
|
result = await replace_async(result, var_regex, async (match, var_name) => {
|
|
479
488
|
// Trim whitespace from variable name
|
|
480
489
|
var_name = var_name.trim();
|
|
490
|
+
|
|
491
|
+
// Check for key=value syntax
|
|
492
|
+
let key = var_name;
|
|
493
|
+
let value: string | undefined = undefined;
|
|
494
|
+
const equals_index = var_name.indexOf('=');
|
|
495
|
+
|
|
496
|
+
if (equals_index !== -1) {
|
|
497
|
+
key = var_name.substring(0, equals_index);
|
|
498
|
+
value = var_name.substring(equals_index + 1);
|
|
499
|
+
}
|
|
500
|
+
|
|
481
501
|
let replacement;
|
|
482
502
|
|
|
483
503
|
if (is_replacer_fn) {
|
|
484
|
-
replacement = await replacements(
|
|
504
|
+
replacement = await replacements(key, value);
|
|
485
505
|
} else {
|
|
486
506
|
// First try direct key lookup (handles hash keys with dots like "hash=.gitignore")
|
|
487
507
|
replacement = replacements[var_name];
|
|
488
508
|
|
|
489
|
-
// If direct lookup fails and
|
|
509
|
+
// If direct lookup fails and we have key=value syntax, try key lookup
|
|
510
|
+
if (replacement === undefined && value !== undefined) {
|
|
511
|
+
replacement = replacements[key];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// If still undefined and variable contains dots, try nested property access
|
|
490
515
|
if (replacement === undefined && var_name.includes('.')) {
|
|
491
516
|
const dot_index = var_name.indexOf('.');
|
|
492
517
|
const base_key = var_name.substring(0, dot_index);
|
|
@@ -499,6 +524,13 @@ export async function parse_template(template: string, replacements: Replacement
|
|
|
499
524
|
}
|
|
500
525
|
}
|
|
501
526
|
|
|
527
|
+
if (replacement !== undefined && typeof replacement === 'function') {
|
|
528
|
+
if (value !== undefined && replacement.length > 0)
|
|
529
|
+
replacement = await replacement(value);
|
|
530
|
+
else
|
|
531
|
+
replacement = await replacement();
|
|
532
|
+
}
|
|
533
|
+
|
|
502
534
|
if (replacement !== undefined)
|
|
503
535
|
return replacement;
|
|
504
536
|
|
|
@@ -549,25 +581,6 @@ export async function get_git_hashes(length = 7): Promise<Record<string, string>
|
|
|
549
581
|
return hash_map;
|
|
550
582
|
}
|
|
551
583
|
|
|
552
|
-
export async function generate_hash_subs(length = 7, prefix = 'hash=', hashes?: Record<string, string>, format?: string): Promise<Record<string, string>> {
|
|
553
|
-
const hash_map: Record<string, string> = {};
|
|
554
|
-
|
|
555
|
-
if (!hashes)
|
|
556
|
-
hashes = await get_git_hashes(length);
|
|
557
|
-
|
|
558
|
-
for (const [file, hash] of Object.entries(hashes)) {
|
|
559
|
-
if (format !== undefined) {
|
|
560
|
-
const formatted_value = format
|
|
561
|
-
.replace(/\$file/g, file)
|
|
562
|
-
.replace(/\$hash/g, hash);
|
|
563
|
-
hash_map[prefix + file] = formatted_value;
|
|
564
|
-
} else {
|
|
565
|
-
hash_map[prefix + file] = hash;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return hash_map;
|
|
570
|
-
}
|
|
571
584
|
// endregion
|
|
572
585
|
|
|
573
586
|
// region serving
|
|
@@ -984,7 +997,7 @@ type WebsocketHandlers = {
|
|
|
984
997
|
drain?: (ws: WebSocket) => void
|
|
985
998
|
};
|
|
986
999
|
|
|
987
|
-
type BootstrapSub =
|
|
1000
|
+
type BootstrapSub = ReplacementValue;
|
|
988
1001
|
|
|
989
1002
|
type BootstrapRoute = {
|
|
990
1003
|
content: string | BunFile;
|
|
@@ -995,12 +1008,7 @@ type BootstrapOptions = {
|
|
|
995
1008
|
base?: string | BunFile;
|
|
996
1009
|
routes: Record<string, BootstrapRoute>;
|
|
997
1010
|
cache?: ReturnType<typeof cache_http> | CacheOptions;
|
|
998
|
-
|
|
999
|
-
length?: number;
|
|
1000
|
-
prefix?: string;
|
|
1001
|
-
format?: string;
|
|
1002
|
-
hashes?: Record<string, string>;
|
|
1003
|
-
};
|
|
1011
|
+
cache_bust?: boolean;
|
|
1004
1012
|
error?: {
|
|
1005
1013
|
use_canary_reporting?: boolean;
|
|
1006
1014
|
error_page: string | BunFile;
|
|
@@ -1420,18 +1428,20 @@ export function http_serve(port: number, hostname?: string) {
|
|
|
1420
1428
|
|
|
1421
1429
|
/* Bootstrap a static web server */
|
|
1422
1430
|
bootstrap: async function(options: BootstrapOptions) {
|
|
1423
|
-
let
|
|
1431
|
+
let git_hash_table: Record<string, string> = {};
|
|
1432
|
+
let cache_bust_subs = {};
|
|
1424
1433
|
|
|
1425
|
-
if (options.
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1434
|
+
if (options.cache_bust) {
|
|
1435
|
+
git_hash_table = await get_git_hashes();
|
|
1436
|
+
cache_bust_subs = {
|
|
1437
|
+
asset: (file: string) => {
|
|
1438
|
+
const hash = git_hash_table[file];
|
|
1439
|
+
return hash ? `${file}?v=${hash}` : file;
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1432
1442
|
}
|
|
1433
1443
|
|
|
1434
|
-
const global_sub_table = sub_table_merge(
|
|
1444
|
+
const global_sub_table = sub_table_merge(cache_bust_subs, options.global_subs);
|
|
1435
1445
|
|
|
1436
1446
|
let cache = options.cache;
|
|
1437
1447
|
if (cache !== undefined && !is_cache_http(cache))
|