@stati/core 1.29.0 → 1.29.1
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/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +76 -43
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +21 -31
- package/dist/core/preview.d.ts.map +1 -1
- package/dist/core/preview.js +21 -32
- package/dist/core/utils/bundle-matching.utils.d.ts +8 -8
- package/dist/core/utils/bundle-matching.utils.d.ts.map +1 -1
- package/dist/core/utils/bundle-matching.utils.js +10 -10
- package/dist/core/utils/index.d.ts +3 -3
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +2 -2
- package/dist/core/utils/server.utils.d.ts +38 -3
- package/dist/core/utils/server.utils.d.ts.map +1 -1
- package/dist/core/utils/server.utils.js +132 -44
- package/dist/core/utils/typescript.utils.d.ts +27 -0
- package/dist/core/utils/typescript.utils.d.ts.map +1 -1
- package/dist/core/utils/typescript.utils.js +34 -1
- package/dist/search/generator.d.ts +18 -7
- package/dist/search/generator.d.ts.map +1 -1
- package/dist/search/generator.js +35 -12
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -1
- package/dist/types/config.d.ts +6 -4
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AAyEA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EAQP,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,iCAAiC;IACjC,OAAO,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACrC,uEAAuE;IACvE,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,sGAAsG;IACtG,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,wDAAwD;IACxD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsB5E"}
|
package/dist/core/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, enableInventoryTracking, disableInventoryTracking, clearInventory, writeTailwindClassInventory, getInventorySize, isTailwindUsed, loadPreviousInventory, compileTypeScript, detectExistingBundles, autoInjectBundles, getBundlePathsForPage, formatBytes, isVirtualPage, getPageOutputFile, } from './utils/index.js';
|
|
1
|
+
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, enableInventoryTracking, disableInventoryTracking, clearInventory, writeTailwindClassInventory, getInventorySize, isTailwindUsed, loadPreviousInventory, compileTypeScript, detectExistingBundles, removeStaleBundleFiles, autoInjectBundles, getBundlePathsForPage, formatBytes, isVirtualPage, getPageOutputFile, } from './utils/index.js';
|
|
2
2
|
import { join, dirname, relative, posix } from 'node:path';
|
|
3
3
|
import { performance } from 'node:perf_hooks';
|
|
4
4
|
import { loadConfig } from '../config/loader.js';
|
|
@@ -11,7 +11,7 @@ import { buildNavigation } from './navigation.js';
|
|
|
11
11
|
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, hasRecordedReads, pruneOrphanedPages, withBuildLock, computeNavigationHash, computeContentHash, clearFileHashCache, clearTemplatePathCache, } from './isg/index.js';
|
|
12
12
|
import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
|
|
13
13
|
import { generateRSSFeeds, validateRSSConfig } from '../rss/index.js';
|
|
14
|
-
import { computeSearchIndexFilename, generateSearchIndex, writeSearchIndex, autoInjectSearchMeta, isGeneratedPageExcludedFromSearch, } from '../search/index.js';
|
|
14
|
+
import { computeSearchIndexFilename, generateSearchIndex, writeSearchIndex, removeStaleSearchIndexes, autoInjectSearchMeta, isGeneratedPageExcludedFromSearch, } from '../search/index.js';
|
|
15
15
|
import { getEnv, isDevelopment } from '../env.js';
|
|
16
16
|
import { DEFAULT_OUT_DIR } from '../constants.js';
|
|
17
17
|
import { ImageRuntime } from '../images/runtime.js';
|
|
@@ -212,6 +212,58 @@ async function loadContentAndBuildNavigation(config, options, logger) {
|
|
|
212
212
|
const eta = createTemplateEngine(config);
|
|
213
213
|
return { pages, markdownFiles: loaded.length, navigation, md, eta, navigationHash };
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Generates the search index from the page models and writes it before any page renders,
|
|
217
|
+
* returning the file name pages link to. Every page links to the index, and the name is part
|
|
218
|
+
* of each page's cache hash, so a production build names a hashed file after the index
|
|
219
|
+
* content: a build that leaves the indexed text alone keeps the name and its cached pages.
|
|
220
|
+
* Development keeps one name, so a content edit does not re-render every page. Writing the
|
|
221
|
+
* index first means it is not held in memory while the pages render.
|
|
222
|
+
*
|
|
223
|
+
* @returns The index file name, or undefined when search is disabled
|
|
224
|
+
*/
|
|
225
|
+
async function writeSearchIndexBeforeRendering(pages, config, md, outDir, logger, recorder) {
|
|
226
|
+
if (config.search?.enabled !== true) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const isDevMode = isDevelopment();
|
|
230
|
+
const endSearchIndexSpan = recorder.startSpan('searchIndexGenerationMs');
|
|
231
|
+
// TOC entries and markdown content, the same data a render produces, without rendering.
|
|
232
|
+
// Sorted by URL: content discovery returns files in a different order on each run, which
|
|
233
|
+
// would change the index content, and with it the name, on every build.
|
|
234
|
+
const tocEnabled = config.markdown?.toc !== false;
|
|
235
|
+
const byUrl = (a, b) => a.url.localeCompare(b.url) || a.sourcePath.localeCompare(b.sourcePath);
|
|
236
|
+
const searchablePages = pages
|
|
237
|
+
.filter((page) => !isGeneratedPageExcludedFromSearch(page))
|
|
238
|
+
.sort(byUrl)
|
|
239
|
+
.map((page) => ({
|
|
240
|
+
page,
|
|
241
|
+
toc: tocEnabled ? extractToc(page.content, md) : [],
|
|
242
|
+
markdownContent: page.content,
|
|
243
|
+
}));
|
|
244
|
+
const index = generateSearchIndex(searchablePages, config.search);
|
|
245
|
+
// A production hash covers the documents and schema version, not generatedAt, which
|
|
246
|
+
// changes on every build; serializing them is skipped when the name has no hash
|
|
247
|
+
const hashInput = isDevMode
|
|
248
|
+
? 'dev'
|
|
249
|
+
: config.search.hashFilename !== false
|
|
250
|
+
? JSON.stringify({ version: index.version, documents: index.documents })
|
|
251
|
+
: undefined;
|
|
252
|
+
const filename = computeSearchIndexFilename(config.search, hashInput);
|
|
253
|
+
endSearchIndexSpan();
|
|
254
|
+
// Pages still link to the name when nothing is searchable, but no file is written
|
|
255
|
+
if (searchablePages.length === 0) {
|
|
256
|
+
return filename;
|
|
257
|
+
}
|
|
258
|
+
logger.info('');
|
|
259
|
+
logger.info(`Generating search index to ${filename}`);
|
|
260
|
+
const writeStart = performance.now();
|
|
261
|
+
// In dev mode, skip write if content hasn't changed (template-only changes)
|
|
262
|
+
const metadata = await writeSearchIndex(index, outDir, filename, isDevMode);
|
|
263
|
+
recorder.recordPhase('searchIndexWriteMs', performance.now() - writeStart);
|
|
264
|
+
logger.success(`Generated search index with ${metadata.documentCount} documents`);
|
|
265
|
+
return filename;
|
|
266
|
+
}
|
|
215
267
|
/**
|
|
216
268
|
* Output file and ISG cache key of a page.
|
|
217
269
|
*/
|
|
@@ -238,10 +290,9 @@ function hashableData(value) {
|
|
|
238
290
|
/**
|
|
239
291
|
* Processes pages with ISG caching logic.
|
|
240
292
|
*/
|
|
241
|
-
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder,
|
|
293
|
+
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder, imageRuntime = null, navigationHash = computeNavigationHash(navigation)) {
|
|
242
294
|
let cacheHits = 0;
|
|
243
295
|
let cacheMisses = 0;
|
|
244
|
-
const searchablePages = [];
|
|
245
296
|
// Build context
|
|
246
297
|
const buildContext = { config, pages };
|
|
247
298
|
// Run beforeAll hook
|
|
@@ -252,6 +303,8 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
252
303
|
}
|
|
253
304
|
// Collection summaries, hashed after beforeAll because the hook can change front matter
|
|
254
305
|
const summaries = createSummaryHasher(pages, resolveSummaryFields(config));
|
|
306
|
+
// Generated after beforeAll for the same reason; pages render with its file name
|
|
307
|
+
const searchIndexFilename = await writeSearchIndexBeforeRendering(pages, config, md, outDir, logger, recorder);
|
|
255
308
|
// The pages of a listing share one array of page URLs, hashed once for all of them
|
|
256
309
|
const urlListHashes = new WeakMap();
|
|
257
310
|
const hashUrlList = (urls) => {
|
|
@@ -360,13 +413,6 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
360
413
|
}
|
|
361
414
|
// Record page timing for cached pages (0ms render time)
|
|
362
415
|
recorder.recordPageTiming(page.url, 0, true);
|
|
363
|
-
// Collect searchable page data for cached pages if search is enabled
|
|
364
|
-
// Extract TOC without full HTML rendering for efficiency
|
|
365
|
-
if (config.search?.enabled === true && !isGeneratedPageExcludedFromSearch(page)) {
|
|
366
|
-
const tocEnabled = config.markdown?.toc !== false;
|
|
367
|
-
const toc = tocEnabled ? extractToc(page.content, md) : [];
|
|
368
|
-
searchablePages.push({ page, toc, markdownContent: page.content });
|
|
369
|
-
}
|
|
370
416
|
continue;
|
|
371
417
|
}
|
|
372
418
|
// Cache miss - need to rebuild
|
|
@@ -465,11 +511,6 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
465
511
|
totalCacheEntryTime += performance.now() - cacheEntryStart;
|
|
466
512
|
}
|
|
467
513
|
}
|
|
468
|
-
// Collect searchable page data if search is enabled
|
|
469
|
-
// Uses TOC entries and markdown content instead of parsing rendered HTML
|
|
470
|
-
if (config.search?.enabled === true && !isGeneratedPageExcludedFromSearch(page)) {
|
|
471
|
-
searchablePages.push({ page, toc, markdownContent: page.content });
|
|
472
|
-
}
|
|
473
514
|
// Run afterRender hook
|
|
474
515
|
if (config.hooks?.afterRender) {
|
|
475
516
|
const hookStart = performance.now();
|
|
@@ -515,7 +556,7 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
515
556
|
logger.showRenderingSummary();
|
|
516
557
|
}
|
|
517
558
|
}
|
|
518
|
-
return { cacheHits, cacheMisses,
|
|
559
|
+
return { cacheHits, cacheMisses, searchIndexFilename };
|
|
519
560
|
}
|
|
520
561
|
/**
|
|
521
562
|
* Copies static assets and returns the count.
|
|
@@ -687,22 +728,15 @@ async function buildInternal(options = {}) {
|
|
|
687
728
|
});
|
|
688
729
|
endTsSpan();
|
|
689
730
|
}
|
|
690
|
-
// Pre-compute search index filename if search is enabled
|
|
691
|
-
// In dev mode, use a stable filename to simlplify testing and debugging
|
|
692
|
-
let searchIndexFilename;
|
|
693
|
-
if (config.search?.enabled) {
|
|
694
|
-
const buildId = isDevMode ? 'dev' : buildStartTime.toString();
|
|
695
|
-
searchIndexFilename = computeSearchIndexFilename(config.search, buildId);
|
|
696
|
-
}
|
|
697
731
|
// Process pages with ISG caching logic
|
|
698
732
|
if (logger.step) {
|
|
699
733
|
console.log(); // Add spacing before page processing
|
|
700
734
|
}
|
|
701
735
|
const endPageRenderSpan = recorder.startSpan('pageRenderingMs');
|
|
702
736
|
const buildTime = new Date();
|
|
703
|
-
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder,
|
|
737
|
+
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder, imageRuntime, navigationHash);
|
|
704
738
|
endPageRenderSpan();
|
|
705
|
-
const { cacheHits, cacheMisses,
|
|
739
|
+
const { cacheHits, cacheMisses, searchIndexFilename } = pageProcessingResult;
|
|
706
740
|
// Remove the output of pages that no longer exist, such as a deleted post or a tag no post
|
|
707
741
|
// uses anymore. Runs before static assets are copied, so a file in the static directory at
|
|
708
742
|
// the same path is copied afterwards.
|
|
@@ -734,23 +768,6 @@ async function buildInternal(options = {}) {
|
|
|
734
768
|
logger.info(`Images: ${generated} generated, ${cached} reused${failed > 0 ? `, ${failed} failed` : ''}`);
|
|
735
769
|
}
|
|
736
770
|
}
|
|
737
|
-
// Generate search index if enabled
|
|
738
|
-
// Uses markdown content and TOC entries
|
|
739
|
-
let searchIndexMetadata;
|
|
740
|
-
if (config.search?.enabled && searchablePages.length > 0 && searchIndexFilename) {
|
|
741
|
-
logger.info('');
|
|
742
|
-
logger.info(`Generating search index to ${searchIndexFilename}`);
|
|
743
|
-
const endSearchIndexSpan = recorder.startSpan('searchIndexGenerationMs');
|
|
744
|
-
const searchIndex = generateSearchIndex(searchablePages, config.search);
|
|
745
|
-
endSearchIndexSpan();
|
|
746
|
-
const writeStart = performance.now();
|
|
747
|
-
// In dev mode, skip write if content hasn't changed (template-only changes)
|
|
748
|
-
const skipIfUnchanged = isDevMode;
|
|
749
|
-
searchIndexMetadata = await writeSearchIndex(searchIndex, outDir, searchIndexFilename, skipIfUnchanged);
|
|
750
|
-
const writeTime = performance.now() - writeStart;
|
|
751
|
-
recorder.recordPhase('searchIndexWriteMs', writeTime);
|
|
752
|
-
logger.success(`Generated search index with ${searchIndexMetadata.documentCount} documents`);
|
|
753
|
-
}
|
|
754
771
|
// Record page rendering counts
|
|
755
772
|
recorder.increment('renderedPages', cacheMisses);
|
|
756
773
|
recorder.increment('cachedPages', cacheHits);
|
|
@@ -783,6 +800,22 @@ async function buildInternal(options = {}) {
|
|
|
783
800
|
.map((failure) => `Open Graph image failed for ${failure.label}: ${failure.message}`)
|
|
784
801
|
.join('\n'));
|
|
785
802
|
}
|
|
803
|
+
// Delete the bundles and search indexes earlier builds wrote under other names, now that
|
|
804
|
+
// every page links to this build's files. Before the static copy, so a file in the static
|
|
805
|
+
// directory with such a name is copied afterwards.
|
|
806
|
+
if (!isDevMode) {
|
|
807
|
+
if (config.typescript?.enabled) {
|
|
808
|
+
await removeStaleBundleFiles({
|
|
809
|
+
projectRoot: process.cwd(),
|
|
810
|
+
config: config.typescript,
|
|
811
|
+
outDir: config.outDir || DEFAULT_OUT_DIR,
|
|
812
|
+
compiled: compiledBundles,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
if (config.search?.enabled && searchIndexFilename) {
|
|
816
|
+
await removeStaleSearchIndexes(outDir, config.search, searchIndexFilename);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
786
819
|
// Copy static assets and count them (skip for template-only changes in dev mode)
|
|
787
820
|
const endAssetSpan = recorder.startSpan('assetCopyMs');
|
|
788
821
|
let assetsCount = 0;
|
package/dist/core/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAoC7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAmZD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAmhBxF"}
|
package/dist/core/dev.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { join, extname } from 'node:path';
|
|
3
2
|
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import { WebSocketServer } from 'ws';
|
|
5
4
|
import chokidar from 'chokidar';
|
|
@@ -9,7 +8,7 @@ import { loadConfig } from '../config/loader.js';
|
|
|
9
8
|
import { clearTemplateEngineCache } from './templates.js';
|
|
10
9
|
import { clearMarkdownProcessorCache } from './markdown.js';
|
|
11
10
|
import { loadCacheManifest, saveCacheManifest, DevServerLockManager } from './isg/index.js';
|
|
12
|
-
import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, createFallbackLogger, mergeServerOptions, createTypeScriptWatcher, isEnabledEnvFlag, refreshCssOutputWatcher, normalizePathForComparison,
|
|
11
|
+
import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, resolveRequestFile, resolveNotFoundPage, getMimeType, parseRequestPath, createErrorOverlay, parseErrorDetails, TemplateError, createFallbackLogger, mergeServerOptions, createTypeScriptWatcher, isEnabledEnvFlag, refreshCssOutputWatcher, normalizePathForComparison, assertPortAvailable, toPortUnavailableError, } from './utils/index.js';
|
|
13
12
|
import { setEnv, getEnv } from '../env.js';
|
|
14
13
|
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION, DEFAULT_OUT_DIR, } from '../constants.js';
|
|
15
14
|
/**
|
|
@@ -367,30 +366,6 @@ export async function createDevServer(options = {}) {
|
|
|
367
366
|
// Diagnostics toggles used for investigating dev-server rebuild slowdown.
|
|
368
367
|
const disableCssWatcher = isEnabledEnvFlag(process.env.STATI_DEV_DISABLE_CSS_WATCHER);
|
|
369
368
|
const disableWsReload = isEnabledEnvFlag(process.env.STATI_DEV_DISABLE_WS_RELOAD);
|
|
370
|
-
/**
|
|
371
|
-
* Gets MIME type for a file based on its extension
|
|
372
|
-
*/
|
|
373
|
-
function getMimeType(filePath) {
|
|
374
|
-
const ext = extname(filePath).toLowerCase();
|
|
375
|
-
const mimeTypes = {
|
|
376
|
-
'.html': 'text/html',
|
|
377
|
-
'.js': 'application/javascript',
|
|
378
|
-
'.css': 'text/css',
|
|
379
|
-
'.json': 'application/json',
|
|
380
|
-
'.png': 'image/png',
|
|
381
|
-
'.jpg': 'image/jpeg',
|
|
382
|
-
'.jpeg': 'image/jpeg',
|
|
383
|
-
'.gif': 'image/gif',
|
|
384
|
-
'.svg': 'image/svg+xml',
|
|
385
|
-
'.ico': 'image/x-icon',
|
|
386
|
-
'.webp': 'image/webp',
|
|
387
|
-
'.woff': 'font/woff',
|
|
388
|
-
'.woff2': 'font/woff2',
|
|
389
|
-
'.ttf': 'font/ttf',
|
|
390
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
391
|
-
};
|
|
392
|
-
return mimeTypes[ext] || 'application/octet-stream';
|
|
393
|
-
}
|
|
394
369
|
/**
|
|
395
370
|
* Injects live reload script into HTML responses
|
|
396
371
|
*/
|
|
@@ -461,9 +436,9 @@ export async function createDevServer(options = {}) {
|
|
|
461
436
|
statusCode: 500,
|
|
462
437
|
};
|
|
463
438
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (
|
|
439
|
+
// Security: the file the request names, or null when it is outside the output directory
|
|
440
|
+
const requestFile = resolveRequestFile(outDir, requestPath);
|
|
441
|
+
if (requestFile === null) {
|
|
467
442
|
return {
|
|
468
443
|
content: '403 - Forbidden',
|
|
469
444
|
mimeType: 'text/plain',
|
|
@@ -471,7 +446,7 @@ export async function createDevServer(options = {}) {
|
|
|
471
446
|
};
|
|
472
447
|
}
|
|
473
448
|
// Use the shared pretty URL resolver
|
|
474
|
-
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath,
|
|
449
|
+
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath, requestFile);
|
|
475
450
|
if (!found || !filePath) {
|
|
476
451
|
// If we have a build error and this is an HTML request, show error overlay instead of 404
|
|
477
452
|
const errorToShow = lastBuildError;
|
|
@@ -508,6 +483,16 @@ export async function createDevServer(options = {}) {
|
|
|
508
483
|
statusCode: 500,
|
|
509
484
|
};
|
|
510
485
|
}
|
|
486
|
+
// Serve the site's 404 page, as static hosts do, with live reload so it updates on edits
|
|
487
|
+
const notFoundPage = await resolveNotFoundPage(outDir);
|
|
488
|
+
if (notFoundPage) {
|
|
489
|
+
const html = await readFile(notFoundPage, 'utf-8');
|
|
490
|
+
return {
|
|
491
|
+
content: injectLiveReloadScript(html),
|
|
492
|
+
mimeType: 'text/html',
|
|
493
|
+
statusCode: 404,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
511
496
|
return {
|
|
512
497
|
content: requestPath.endsWith('/')
|
|
513
498
|
? '404 - Directory listing not available'
|
|
@@ -601,12 +586,17 @@ export async function createDevServer(options = {}) {
|
|
|
601
586
|
await performInitialBuild(configPath, logger, setLastBuildError);
|
|
602
587
|
// Create HTTP server
|
|
603
588
|
httpServer = createServer(async (req, res) => {
|
|
604
|
-
const requestPath = req.url
|
|
589
|
+
const requestPath = parseRequestPath(req.url);
|
|
605
590
|
const requestStart = Date.now();
|
|
606
591
|
// Handle WebSocket upgrade path
|
|
607
592
|
if (requestPath === '/__ws') {
|
|
608
593
|
return; // Let WebSocket server handle this
|
|
609
594
|
}
|
|
595
|
+
if (requestPath === null) {
|
|
596
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
597
|
+
res.end('400 - Bad Request');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
610
600
|
// Only log page requests, not static assets (files with extensions)
|
|
611
601
|
const hasFileExtension = requestPath.includes('.') && !requestPath.endsWith('.html');
|
|
612
602
|
if (!hasFileExtension) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,mBAAmB,CAAC;AAgB7D,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAyBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAwJxB"}
|
package/dist/core/preview.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { join, extname } from 'node:path';
|
|
3
2
|
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import { loadConfig } from '../config/loader.js';
|
|
5
|
-
import { resolveDevPaths, resolvePrettyUrl, createFallbackLogger, mergeServerOptions,
|
|
4
|
+
import { resolveDevPaths, resolvePrettyUrl, resolveRequestFile, resolveNotFoundPage, getMimeType, parseRequestPath, createFallbackLogger, mergeServerOptions, assertPortAvailable, toPortUnavailableError, } from './utils/index.js';
|
|
6
5
|
import { DEFAULT_PREVIEW_PORT, DEFAULT_DEV_HOST } from '../constants.js';
|
|
7
6
|
/**
|
|
8
7
|
* Loads and validates configuration for the preview server.
|
|
@@ -41,37 +40,13 @@ export async function createPreviewServer(options = {}) {
|
|
|
41
40
|
});
|
|
42
41
|
const url = `http://${host}:${port}`;
|
|
43
42
|
let httpServer = null;
|
|
44
|
-
/**
|
|
45
|
-
* Gets MIME type for a file based on its extension
|
|
46
|
-
*/
|
|
47
|
-
function getMimeType(filePath) {
|
|
48
|
-
const ext = extname(filePath).toLowerCase();
|
|
49
|
-
const mimeTypes = {
|
|
50
|
-
'.html': 'text/html',
|
|
51
|
-
'.js': 'application/javascript',
|
|
52
|
-
'.css': 'text/css',
|
|
53
|
-
'.json': 'application/json',
|
|
54
|
-
'.png': 'image/png',
|
|
55
|
-
'.jpg': 'image/jpeg',
|
|
56
|
-
'.jpeg': 'image/jpeg',
|
|
57
|
-
'.gif': 'image/gif',
|
|
58
|
-
'.svg': 'image/svg+xml',
|
|
59
|
-
'.ico': 'image/x-icon',
|
|
60
|
-
'.webp': 'image/webp',
|
|
61
|
-
'.woff': 'font/woff',
|
|
62
|
-
'.woff2': 'font/woff2',
|
|
63
|
-
'.ttf': 'font/ttf',
|
|
64
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
65
|
-
};
|
|
66
|
-
return mimeTypes[ext] || 'application/octet-stream';
|
|
67
|
-
}
|
|
68
43
|
/**
|
|
69
44
|
* Serves files from the dist directory
|
|
70
45
|
*/
|
|
71
46
|
async function serveFile(requestPath) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
47
|
+
// Security: the file the request names, or null when it is outside the output directory
|
|
48
|
+
const requestFile = resolveRequestFile(outDir, requestPath);
|
|
49
|
+
if (requestFile === null) {
|
|
75
50
|
return {
|
|
76
51
|
content: '403 - Forbidden',
|
|
77
52
|
mimeType: 'text/plain',
|
|
@@ -79,8 +54,17 @@ export async function createPreviewServer(options = {}) {
|
|
|
79
54
|
};
|
|
80
55
|
}
|
|
81
56
|
// Use the shared pretty URL resolver
|
|
82
|
-
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath,
|
|
57
|
+
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath, requestFile);
|
|
83
58
|
if (!found || !filePath) {
|
|
59
|
+
// Serve the site's 404 page, as static hosts do
|
|
60
|
+
const notFoundPage = await resolveNotFoundPage(outDir);
|
|
61
|
+
if (notFoundPage) {
|
|
62
|
+
return {
|
|
63
|
+
content: await readFile(notFoundPage),
|
|
64
|
+
mimeType: 'text/html',
|
|
65
|
+
statusCode: 404,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
84
68
|
return {
|
|
85
69
|
content: requestPath.endsWith('/')
|
|
86
70
|
? '404 - Directory listing not available'
|
|
@@ -114,8 +98,13 @@ export async function createPreviewServer(options = {}) {
|
|
|
114
98
|
await assertPortAvailable(port, host, 'preview.port');
|
|
115
99
|
// Create HTTP server
|
|
116
100
|
httpServer = createServer(async (req, res) => {
|
|
117
|
-
|
|
118
|
-
|
|
101
|
+
logger.processing?.(`${req.method} ${req.url || '/'}`);
|
|
102
|
+
const requestPath = parseRequestPath(req.url);
|
|
103
|
+
if (requestPath === null) {
|
|
104
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
105
|
+
res.end('400 - Bad Request');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
119
108
|
try {
|
|
120
109
|
const { content, mimeType, statusCode } = await serveFile(requestPath);
|
|
121
110
|
res.writeHead(statusCode, {
|
|
@@ -55,7 +55,7 @@ export interface CompiledBundleInfo {
|
|
|
55
55
|
* 3. If page matches any `exclude` pattern: bundle is excluded (exclude takes precedence)
|
|
56
56
|
* 4. Bundle order from config is preserved
|
|
57
57
|
*
|
|
58
|
-
* @param
|
|
58
|
+
* @param pageUrl - The page URL (e.g., '/docs/api/hooks'), not its output file
|
|
59
59
|
* @param bundles - Array of bundle configurations to filter
|
|
60
60
|
* @returns Array of bundles that match this page, in config order
|
|
61
61
|
*
|
|
@@ -67,22 +67,22 @@ export interface CompiledBundleInfo {
|
|
|
67
67
|
* { entryPoint: 'playground.ts', bundleName: 'playground', include: ['/examples/**'], exclude: ['/examples/simple/**'] }
|
|
68
68
|
* ];
|
|
69
69
|
*
|
|
70
|
-
* matchBundlesForPage('/docs/api/hooks
|
|
70
|
+
* matchBundlesForPage('/docs/api/hooks', bundles);
|
|
71
71
|
* // Returns: [{ entryPoint: 'core.ts', ... }, { entryPoint: 'docs.ts', ... }]
|
|
72
72
|
*
|
|
73
|
-
* matchBundlesForPage('/examples/advanced/demo
|
|
73
|
+
* matchBundlesForPage('/examples/advanced/demo', bundles);
|
|
74
74
|
* // Returns: [{ entryPoint: 'core.ts', ... }, { entryPoint: 'playground.ts', ... }]
|
|
75
75
|
*
|
|
76
|
-
* matchBundlesForPage('/examples/simple/basic
|
|
76
|
+
* matchBundlesForPage('/examples/simple/basic', bundles);
|
|
77
77
|
* // Returns: [{ entryPoint: 'core.ts', ... }] - playground excluded
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
|
-
export declare function matchBundlesForPage(
|
|
80
|
+
export declare function matchBundlesForPage(pageUrl: string, bundles: BundleConfig[]): BundleConfig[];
|
|
81
81
|
/**
|
|
82
82
|
* Gets bundle paths for a page from compiled bundle info.
|
|
83
83
|
* Filters compiled bundles based on page matching and returns only the paths.
|
|
84
84
|
*
|
|
85
|
-
* @param
|
|
85
|
+
* @param pageUrl - The page URL (e.g., '/docs/api/hooks'), not its output file
|
|
86
86
|
* @param compiledBundles - Array of compiled bundle info
|
|
87
87
|
* @returns Array of bundle paths matched for this page, in config order
|
|
88
88
|
*
|
|
@@ -93,9 +93,9 @@ export declare function matchBundlesForPage(pageOutputPath: string, bundles: Bun
|
|
|
93
93
|
* { config: { entryPoint: 'docs.ts', bundleName: 'docs', include: ['/docs/**'] }, filename: 'docs-def.js', path: '/_assets/docs-def.js' }
|
|
94
94
|
* ];
|
|
95
95
|
*
|
|
96
|
-
* getBundlePathsForPage('/docs/api/hooks
|
|
96
|
+
* getBundlePathsForPage('/docs/api/hooks', compiled);
|
|
97
97
|
* // Returns: ['/_assets/core-abc.js', '/_assets/docs-def.js']
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
|
-
export declare function getBundlePathsForPage(
|
|
100
|
+
export declare function getBundlePathsForPage(pageUrl: string, compiledBundles: CompiledBundleInfo[]): string[];
|
|
101
101
|
//# sourceMappingURL=bundle-matching.utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle-matching.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/bundle-matching.utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,UAAU,EAAE,MAAM,EAAE;CAOjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAqBvE;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,MAAM,EAAE,YAAY,CAAC;IACrB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"bundle-matching.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/bundle-matching.utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,UAAU,EAAE,MAAM,EAAE;CAOjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAqBvE;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,MAAM,EAAE,YAAY,CAAC;IACrB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAyB5F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,kBAAkB,EAAE,GACpC,MAAM,EAAE,CAaV"}
|
|
@@ -64,7 +64,7 @@ export function validateUniqueBundleNames(bundles) {
|
|
|
64
64
|
* 3. If page matches any `exclude` pattern: bundle is excluded (exclude takes precedence)
|
|
65
65
|
* 4. Bundle order from config is preserved
|
|
66
66
|
*
|
|
67
|
-
* @param
|
|
67
|
+
* @param pageUrl - The page URL (e.g., '/docs/api/hooks'), not its output file
|
|
68
68
|
* @param bundles - Array of bundle configurations to filter
|
|
69
69
|
* @returns Array of bundles that match this page, in config order
|
|
70
70
|
*
|
|
@@ -76,22 +76,22 @@ export function validateUniqueBundleNames(bundles) {
|
|
|
76
76
|
* { entryPoint: 'playground.ts', bundleName: 'playground', include: ['/examples/**'], exclude: ['/examples/simple/**'] }
|
|
77
77
|
* ];
|
|
78
78
|
*
|
|
79
|
-
* matchBundlesForPage('/docs/api/hooks
|
|
79
|
+
* matchBundlesForPage('/docs/api/hooks', bundles);
|
|
80
80
|
* // Returns: [{ entryPoint: 'core.ts', ... }, { entryPoint: 'docs.ts', ... }]
|
|
81
81
|
*
|
|
82
|
-
* matchBundlesForPage('/examples/advanced/demo
|
|
82
|
+
* matchBundlesForPage('/examples/advanced/demo', bundles);
|
|
83
83
|
* // Returns: [{ entryPoint: 'core.ts', ... }, { entryPoint: 'playground.ts', ... }]
|
|
84
84
|
*
|
|
85
|
-
* matchBundlesForPage('/examples/simple/basic
|
|
85
|
+
* matchBundlesForPage('/examples/simple/basic', bundles);
|
|
86
86
|
* // Returns: [{ entryPoint: 'core.ts', ... }] - playground excluded
|
|
87
87
|
* ```
|
|
88
88
|
*/
|
|
89
|
-
export function matchBundlesForPage(
|
|
89
|
+
export function matchBundlesForPage(pageUrl, bundles) {
|
|
90
90
|
if (!bundles || bundles.length === 0) {
|
|
91
91
|
return [];
|
|
92
92
|
}
|
|
93
93
|
// Normalize path to use forward slashes
|
|
94
|
-
const normalizedPath =
|
|
94
|
+
const normalizedPath = pageUrl.replace(/\\/g, '/');
|
|
95
95
|
return bundles.filter((bundle) => {
|
|
96
96
|
// Check exclude patterns first (they take precedence)
|
|
97
97
|
if (bundle.exclude && bundle.exclude.length > 0) {
|
|
@@ -112,7 +112,7 @@ export function matchBundlesForPage(pageOutputPath, bundles) {
|
|
|
112
112
|
* Gets bundle paths for a page from compiled bundle info.
|
|
113
113
|
* Filters compiled bundles based on page matching and returns only the paths.
|
|
114
114
|
*
|
|
115
|
-
* @param
|
|
115
|
+
* @param pageUrl - The page URL (e.g., '/docs/api/hooks'), not its output file
|
|
116
116
|
* @param compiledBundles - Array of compiled bundle info
|
|
117
117
|
* @returns Array of bundle paths matched for this page, in config order
|
|
118
118
|
*
|
|
@@ -123,15 +123,15 @@ export function matchBundlesForPage(pageOutputPath, bundles) {
|
|
|
123
123
|
* { config: { entryPoint: 'docs.ts', bundleName: 'docs', include: ['/docs/**'] }, filename: 'docs-def.js', path: '/_assets/docs-def.js' }
|
|
124
124
|
* ];
|
|
125
125
|
*
|
|
126
|
-
* getBundlePathsForPage('/docs/api/hooks
|
|
126
|
+
* getBundlePathsForPage('/docs/api/hooks', compiled);
|
|
127
127
|
* // Returns: ['/_assets/core-abc.js', '/_assets/docs-def.js']
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
|
130
|
-
export function getBundlePathsForPage(
|
|
130
|
+
export function getBundlePathsForPage(pageUrl, compiledBundles) {
|
|
131
131
|
if (!compiledBundles || compiledBundles.length === 0) {
|
|
132
132
|
return [];
|
|
133
133
|
}
|
|
134
|
-
const matchedConfigs = matchBundlesForPage(
|
|
134
|
+
const matchedConfigs = matchBundlesForPage(pageUrl, compiledBundles.map((b) => b.config));
|
|
135
135
|
// Map matched configs back to their paths, preserving order
|
|
136
136
|
const matchedNames = new Set(matchedConfigs.map((c) => c.bundleName));
|
|
137
137
|
return compiledBundles.filter((b) => matchedNames.has(b.config.bundleName)).map((b) => b.path);
|
|
@@ -15,7 +15,7 @@ export { makeCallablePartial, wrapPartialsAsCallable } from './callable-partials
|
|
|
15
15
|
export type { CallablePartial } from './callable-partials.utils.js';
|
|
16
16
|
export { TemplateError, parseEtaError, createTemplateError } from './template-errors.utils.js';
|
|
17
17
|
export { createNavigationHelpers } from './navigation-helpers.utils.js';
|
|
18
|
-
export { resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
18
|
+
export { getMimeType, parseRequestPath, resolveNotFoundPage, resolveRequestFile, resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
19
19
|
export type { PrettyUrlResult, MergedServerOptions, MergeServerOptionsParams, PortUnavailableCode, } from './server.utils.js';
|
|
20
20
|
export { createErrorOverlay, parseErrorDetails } from './error-overlay.utils.js';
|
|
21
21
|
export type { ErrorDetails } from './error-overlay.utils.js';
|
|
@@ -24,8 +24,8 @@ export { globToRegex, matchesGlob } from './glob-patterns.utils.js';
|
|
|
24
24
|
export { matchBundlesForPage, getBundlePathsForPage, validateUniqueBundleNames, DuplicateBundleNameError, } from './bundle-matching.utils.js';
|
|
25
25
|
export type { CompiledBundleInfo } from './bundle-matching.utils.js';
|
|
26
26
|
export { createFallbackLogger } from './logger.utils.js';
|
|
27
|
-
export { compileTypeScript, createTypeScriptWatcher, compileStatiConfig, cleanupCompiledConfig, autoInjectBundles, formatBytes, detectExistingBundles, } from './typescript.utils.js';
|
|
28
|
-
export type { CompileOptions, WatchOptions } from './typescript.utils.js';
|
|
27
|
+
export { compileTypeScript, createTypeScriptWatcher, compileStatiConfig, cleanupCompiledConfig, autoInjectBundles, formatBytes, detectExistingBundles, removeStaleBundleFiles, } from './typescript.utils.js';
|
|
28
|
+
export type { CompileOptions, WatchOptions, RemoveStaleBundlesOptions, } from './typescript.utils.js';
|
|
29
29
|
export { findHeadClosePosition, injectBeforeHeadClose } from './html.utils.js';
|
|
30
30
|
export { getFrontMatterDate, PUBLISHED_DATE_FIELDS } from './date.utils.js';
|
|
31
31
|
export { isEnabledEnvFlag } from './env.utils.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAG9E,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGxE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACjF,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGpE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"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"}
|
package/dist/core/utils/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { TemplateError, parseEtaError, createTemplateError } from './template-er
|
|
|
27
27
|
// Navigation helper utilities
|
|
28
28
|
export { createNavigationHelpers } from './navigation-helpers.utils.js';
|
|
29
29
|
// Server utilities
|
|
30
|
-
export { resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
30
|
+
export { getMimeType, parseRequestPath, resolveNotFoundPage, resolveRequestFile, resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
31
31
|
// Error overlay utilities
|
|
32
32
|
export { createErrorOverlay, parseErrorDetails } from './error-overlay.utils.js';
|
|
33
33
|
// Version utilities
|
|
@@ -39,7 +39,7 @@ export { matchBundlesForPage, getBundlePathsForPage, validateUniqueBundleNames,
|
|
|
39
39
|
// Logger utilities
|
|
40
40
|
export { createFallbackLogger } from './logger.utils.js';
|
|
41
41
|
// TypeScript compilation utilities
|
|
42
|
-
export { compileTypeScript, createTypeScriptWatcher, compileStatiConfig, cleanupCompiledConfig, autoInjectBundles, formatBytes, detectExistingBundles, } from './typescript.utils.js';
|
|
42
|
+
export { compileTypeScript, createTypeScriptWatcher, compileStatiConfig, cleanupCompiledConfig, autoInjectBundles, formatBytes, detectExistingBundles, removeStaleBundleFiles, } from './typescript.utils.js';
|
|
43
43
|
// HTML manipulation utilities
|
|
44
44
|
export { findHeadClosePosition, injectBeforeHeadClose } from './html.utils.js';
|
|
45
45
|
// Date utilities
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the content type for a file from its extension.
|
|
3
|
+
*
|
|
4
|
+
* @param filePath - Path or file name of the file being served
|
|
5
|
+
* @returns The content type, or `application/octet-stream` for unknown extensions
|
|
6
|
+
*/
|
|
7
|
+
export declare function getMimeType(filePath: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Turns a request URL into the path of the file it asks for: the query string is dropped and
|
|
10
|
+
* percent-escapes are decoded, so `/about?ref=x` asks for `/about` and `/my%20file.pdf` for
|
|
11
|
+
* `/my file.pdf`.
|
|
12
|
+
*
|
|
13
|
+
* @param url - The request URL as received (`req.url`)
|
|
14
|
+
* @returns The decoded path, or null when the URL is malformed (a bad escape or a NUL byte)
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseRequestPath(url: string | undefined): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Finds the site's custom not-found page, the one static hosts serve for a missing URL.
|
|
19
|
+
*
|
|
20
|
+
* @param outDir - The output directory being served
|
|
21
|
+
* @returns The path of `404.html` (or `404/index.html` for a `/404/` permalink), or null
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveNotFoundPage(outDir: string): Promise<string | null>;
|
|
1
24
|
/**
|
|
2
25
|
* Error codes for which a port is considered unavailable
|
|
3
26
|
*/
|
|
@@ -42,6 +65,17 @@ export interface PrettyUrlResult {
|
|
|
42
65
|
/** Whether the path was found */
|
|
43
66
|
found: boolean;
|
|
44
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolves a request path to the file it names inside the served directory. This is where a
|
|
70
|
+
* request path becomes a file system path: every file the servers read is this path or
|
|
71
|
+
* derived from it, so no request reaches outside the directory, whether through `..`
|
|
72
|
+
* segments (raw or percent-encoded), backslashes or an absolute path.
|
|
73
|
+
*
|
|
74
|
+
* @param outDir - The directory being served
|
|
75
|
+
* @param requestPath - The decoded request path, from {@link parseRequestPath}
|
|
76
|
+
* @returns The absolute path, or null when it points outside the directory
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveRequestFile(outDir: string, requestPath: string): string | null;
|
|
45
79
|
/**
|
|
46
80
|
* Attempts to resolve pretty URL paths by trying various fallback strategies.
|
|
47
81
|
* This handles common patterns like:
|
|
@@ -50,11 +84,12 @@ export interface PrettyUrlResult {
|
|
|
50
84
|
* - /path -> /path.html (when original path is not found and has no extension)
|
|
51
85
|
*
|
|
52
86
|
* @param outDir The output directory to serve files from
|
|
53
|
-
* @param requestPath The requested URL path
|
|
54
|
-
* @param
|
|
87
|
+
* @param requestPath The requested URL path, which decides the fallbacks
|
|
88
|
+
* @param requestFile The file the request names, from {@link resolveRequestFile}. Every
|
|
89
|
+
* candidate is derived from it, so none is outside the output directory
|
|
55
90
|
* @returns Promise resolving to the file path to serve or null if not found
|
|
56
91
|
*/
|
|
57
|
-
export declare function resolvePrettyUrl(outDir: string, requestPath: string,
|
|
92
|
+
export declare function resolvePrettyUrl(outDir: string, requestPath: string, requestFile: string): Promise<PrettyUrlResult>;
|
|
58
93
|
/**
|
|
59
94
|
* Options for merging server configuration
|
|
60
95
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.utils.ts"],"names":[],"mappings":"AAsCA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQvE;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhF;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE1D;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBAEvB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,MAAM;CAatF;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAMT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjG;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWrF;AAkBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,sCAAsC;IACtC,OAAO,EAAE,CAAC,CAAC;IACX,4CAA4C;IAC5C,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACrE,4EAA4E;IAC5E,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3F,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAClC,mBAAmB,CAQrB"}
|
|
@@ -1,6 +1,85 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
1
|
+
import { extname, join, resolve, sep } from 'node:path';
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
3
|
import { createServer } from 'node:net';
|
|
4
|
+
/**
|
|
5
|
+
* Content types the dev and preview servers send, by file extension. Covers the files Stati
|
|
6
|
+
* generates (pages, bundles, search index, sitemap, robots.txt, feeds, responsive images) and
|
|
7
|
+
* common static assets; anything else is sent as `application/octet-stream`.
|
|
8
|
+
*/
|
|
9
|
+
const MIME_TYPES = {
|
|
10
|
+
'.html': 'text/html',
|
|
11
|
+
'.js': 'application/javascript',
|
|
12
|
+
'.mjs': 'application/javascript',
|
|
13
|
+
'.css': 'text/css',
|
|
14
|
+
'.json': 'application/json',
|
|
15
|
+
'.map': 'application/json',
|
|
16
|
+
'.webmanifest': 'application/manifest+json',
|
|
17
|
+
'.xml': 'application/xml',
|
|
18
|
+
'.txt': 'text/plain',
|
|
19
|
+
'.png': 'image/png',
|
|
20
|
+
'.jpg': 'image/jpeg',
|
|
21
|
+
'.jpeg': 'image/jpeg',
|
|
22
|
+
'.gif': 'image/gif',
|
|
23
|
+
'.svg': 'image/svg+xml',
|
|
24
|
+
'.ico': 'image/x-icon',
|
|
25
|
+
'.webp': 'image/webp',
|
|
26
|
+
'.avif': 'image/avif',
|
|
27
|
+
'.woff': 'font/woff',
|
|
28
|
+
'.woff2': 'font/woff2',
|
|
29
|
+
'.ttf': 'font/ttf',
|
|
30
|
+
'.otf': 'font/otf',
|
|
31
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
32
|
+
'.pdf': 'application/pdf',
|
|
33
|
+
'.mp4': 'video/mp4',
|
|
34
|
+
'.webm': 'video/webm',
|
|
35
|
+
'.wasm': 'application/wasm',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Gets the content type for a file from its extension.
|
|
39
|
+
*
|
|
40
|
+
* @param filePath - Path or file name of the file being served
|
|
41
|
+
* @returns The content type, or `application/octet-stream` for unknown extensions
|
|
42
|
+
*/
|
|
43
|
+
export function getMimeType(filePath) {
|
|
44
|
+
return MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Turns a request URL into the path of the file it asks for: the query string is dropped and
|
|
48
|
+
* percent-escapes are decoded, so `/about?ref=x` asks for `/about` and `/my%20file.pdf` for
|
|
49
|
+
* `/my file.pdf`.
|
|
50
|
+
*
|
|
51
|
+
* @param url - The request URL as received (`req.url`)
|
|
52
|
+
* @returns The decoded path, or null when the URL is malformed (a bad escape or a NUL byte)
|
|
53
|
+
*/
|
|
54
|
+
export function parseRequestPath(url) {
|
|
55
|
+
const rawPath = (url || '/').split(/[?#]/, 1)[0] || '/';
|
|
56
|
+
try {
|
|
57
|
+
const path = decodeURIComponent(rawPath);
|
|
58
|
+
return path.includes('\0') ? null : path;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Finds the site's custom not-found page, the one static hosts serve for a missing URL.
|
|
66
|
+
*
|
|
67
|
+
* @param outDir - The output directory being served
|
|
68
|
+
* @returns The path of `404.html` (or `404/index.html` for a `/404/` permalink), or null
|
|
69
|
+
*/
|
|
70
|
+
export async function resolveNotFoundPage(outDir) {
|
|
71
|
+
for (const candidate of [join(outDir, '404.html'), join(outDir, '404', 'index.html')]) {
|
|
72
|
+
try {
|
|
73
|
+
if ((await stat(candidate)).isFile()) {
|
|
74
|
+
return candidate;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Not there; try the next one
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
4
83
|
/**
|
|
5
84
|
* Thrown when a server cannot bind to its configured port.
|
|
6
85
|
* The message is user-facing and explains how to pick a different port.
|
|
@@ -59,6 +138,44 @@ export function assertPortAvailable(port, host, configKey) {
|
|
|
59
138
|
probe.listen(port, host);
|
|
60
139
|
});
|
|
61
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolves a request path to the file it names inside the served directory. This is where a
|
|
143
|
+
* request path becomes a file system path: every file the servers read is this path or
|
|
144
|
+
* derived from it, so no request reaches outside the directory, whether through `..`
|
|
145
|
+
* segments (raw or percent-encoded), backslashes or an absolute path.
|
|
146
|
+
*
|
|
147
|
+
* @param outDir - The directory being served
|
|
148
|
+
* @param requestPath - The decoded request path, from {@link parseRequestPath}
|
|
149
|
+
* @returns The absolute path, or null when it points outside the directory
|
|
150
|
+
*/
|
|
151
|
+
export function resolveRequestFile(outDir, requestPath) {
|
|
152
|
+
const root = resolve(outDir);
|
|
153
|
+
const filePath = resolve(root, requestPath.replace(/^[/\\]+/, ''));
|
|
154
|
+
if (filePath === root) {
|
|
155
|
+
return root;
|
|
156
|
+
}
|
|
157
|
+
const rootPrefix = root.endsWith(sep) ? root : root + sep;
|
|
158
|
+
if (!filePath.startsWith(rootPrefix)) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return filePath;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The result for a candidate file: found when it exists and is a file.
|
|
165
|
+
*/
|
|
166
|
+
async function fileResult(candidate) {
|
|
167
|
+
if (candidate !== null) {
|
|
168
|
+
try {
|
|
169
|
+
if ((await stat(candidate)).isFile()) {
|
|
170
|
+
return { filePath: candidate, found: true };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Not there
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { filePath: null, found: false };
|
|
178
|
+
}
|
|
62
179
|
/**
|
|
63
180
|
* Attempts to resolve pretty URL paths by trying various fallback strategies.
|
|
64
181
|
* This handles common patterns like:
|
|
@@ -67,16 +184,20 @@ export function assertPortAvailable(port, host, configKey) {
|
|
|
67
184
|
* - /path -> /path.html (when original path is not found and has no extension)
|
|
68
185
|
*
|
|
69
186
|
* @param outDir The output directory to serve files from
|
|
70
|
-
* @param requestPath The requested URL path
|
|
71
|
-
* @param
|
|
187
|
+
* @param requestPath The requested URL path, which decides the fallbacks
|
|
188
|
+
* @param requestFile The file the request names, from {@link resolveRequestFile}. Every
|
|
189
|
+
* candidate is derived from it, so none is outside the output directory
|
|
72
190
|
* @returns Promise resolving to the file path to serve or null if not found
|
|
73
191
|
*/
|
|
74
|
-
export async function resolvePrettyUrl(outDir, requestPath,
|
|
192
|
+
export async function resolvePrettyUrl(outDir, requestPath, requestFile) {
|
|
193
|
+
// The sibling .html file: /blog/ and /blog are served from blog.html. The output
|
|
194
|
+
// directory itself has none, as its sibling is outside it.
|
|
195
|
+
const htmlFile = requestFile === resolve(outDir) ? null : `${requestFile}.html`;
|
|
75
196
|
try {
|
|
76
|
-
const stats = await stat(
|
|
197
|
+
const stats = await stat(requestFile);
|
|
77
198
|
if (stats.isDirectory()) {
|
|
78
199
|
// Try to serve index.html from directory
|
|
79
|
-
const indexPath = join(
|
|
200
|
+
const indexPath = join(requestFile, 'index.html');
|
|
80
201
|
try {
|
|
81
202
|
await stat(indexPath);
|
|
82
203
|
return { filePath: indexPath, found: true };
|
|
@@ -84,50 +205,17 @@ export async function resolvePrettyUrl(outDir, requestPath, originalFilePath) {
|
|
|
84
205
|
catch {
|
|
85
206
|
// If no index.html in directory, try to serve corresponding .html file
|
|
86
207
|
// For example: /examples/ -> examples.html
|
|
87
|
-
|
|
88
|
-
const fallbackPath = join(outDir, `${directoryName}.html`);
|
|
89
|
-
try {
|
|
90
|
-
await stat(fallbackPath);
|
|
91
|
-
return { filePath: fallbackPath, found: true };
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return { filePath: null, found: false };
|
|
95
|
-
}
|
|
208
|
+
return await fileResult(htmlFile);
|
|
96
209
|
}
|
|
97
210
|
}
|
|
98
211
|
// File exists and is not a directory
|
|
99
|
-
return { filePath:
|
|
212
|
+
return { filePath: requestFile, found: true };
|
|
100
213
|
}
|
|
101
214
|
catch {
|
|
102
|
-
// File not found
|
|
103
|
-
if (requestPath.endsWith('/')) {
|
|
104
|
-
|
|
105
|
-
const pathWithoutSlash = requestPath.slice(0, -1);
|
|
106
|
-
const htmlPath = join(outDir, `${pathWithoutSlash}.html`);
|
|
107
|
-
try {
|
|
108
|
-
const stats = await stat(htmlPath);
|
|
109
|
-
if (stats.isFile()) {
|
|
110
|
-
return { filePath: htmlPath, found: true };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
// Continue to not found
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else if (!requestPath.includes('.')) {
|
|
118
|
-
// For requests without trailing slash and without extension, try .html
|
|
119
|
-
const htmlPath = join(outDir, `${requestPath}.html`);
|
|
120
|
-
try {
|
|
121
|
-
const stats = await stat(htmlPath);
|
|
122
|
-
if (stats.isFile()) {
|
|
123
|
-
return { filePath: htmlPath, found: true };
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Continue to not found
|
|
128
|
-
}
|
|
215
|
+
// File not found: try the .html file for /path/ and for /path without an extension
|
|
216
|
+
if (requestPath.endsWith('/') || !requestPath.includes('.')) {
|
|
217
|
+
return await fileResult(htmlFile);
|
|
129
218
|
}
|
|
130
|
-
// No fallback worked
|
|
131
219
|
return { filePath: null, found: false };
|
|
132
220
|
}
|
|
133
221
|
}
|
|
@@ -57,6 +57,33 @@ export interface WatchOptions {
|
|
|
57
57
|
* @returns Array of detected bundle info
|
|
58
58
|
*/
|
|
59
59
|
export declare function detectExistingBundles(options: Omit<CompileOptions, 'logger'>): Promise<CompiledBundleInfo[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Options for {@link removeStaleBundleFiles}.
|
|
62
|
+
*/
|
|
63
|
+
export interface RemoveStaleBundlesOptions {
|
|
64
|
+
/** Root directory of the project */
|
|
65
|
+
projectRoot: string;
|
|
66
|
+
/** TypeScript configuration */
|
|
67
|
+
config: TypeScriptConfig;
|
|
68
|
+
/** Output directory (defaults to 'dist') */
|
|
69
|
+
outDir?: string;
|
|
70
|
+
/** The bundles this build compiled */
|
|
71
|
+
compiled: CompiledBundleInfo[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Deletes the files of compiled bundles that earlier builds left in the bundle output
|
|
75
|
+
* directory: hashed copies from builds whose code differed, and the unhashed file and source
|
|
76
|
+
* map the dev server writes, which production pages never load. Only files named after a
|
|
77
|
+
* bundle compiled in this build are deleted, and never a file this build wrote, so other files
|
|
78
|
+
* in the directory stay.
|
|
79
|
+
*
|
|
80
|
+
* The build calls this after every page is written: until then, pages from the previous
|
|
81
|
+
* build still load the old files.
|
|
82
|
+
*
|
|
83
|
+
* @param options - The project, its TypeScript config and output directory, and the bundles
|
|
84
|
+
* this build compiled
|
|
85
|
+
*/
|
|
86
|
+
export declare function removeStaleBundleFiles(options: RemoveStaleBundlesOptions): Promise<void>;
|
|
60
87
|
/**
|
|
61
88
|
* Compile TypeScript files using esbuild.
|
|
62
89
|
* Supports multiple bundles with parallel compilation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/typescript.utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQrD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,SAAS,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAuCD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GACtC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAyC/B;AAuED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkD9F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAyHjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB5E;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAqC7D;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CA8B7E"}
|
|
1
|
+
{"version":3,"file":"typescript.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/typescript.utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQrD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,SAAS,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAuCD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GACtC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAyC/B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB9F;AAuED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkD9F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAyHjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB5E;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAqC7D;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CA8B7E"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import * as esbuild from 'esbuild';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import * as fs from 'node:fs/promises';
|
|
10
|
-
import { pathExists } from './fs.utils.js';
|
|
10
|
+
import { pathExists, readdir, remove } from './fs.utils.js';
|
|
11
11
|
import { validateUniqueBundleNames } from './bundle-matching.utils.js';
|
|
12
12
|
import { DEFAULT_TS_SRC_DIR, DEFAULT_TS_OUT_DIR, DEFAULT_BUNDLES, DEFAULT_OUT_DIR, } from '../../constants.js';
|
|
13
13
|
/**
|
|
@@ -83,6 +83,39 @@ export async function detectExistingBundles(options) {
|
|
|
83
83
|
}
|
|
84
84
|
return results;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Deletes the files of compiled bundles that earlier builds left in the bundle output
|
|
88
|
+
* directory: hashed copies from builds whose code differed, and the unhashed file and source
|
|
89
|
+
* map the dev server writes, which production pages never load. Only files named after a
|
|
90
|
+
* bundle compiled in this build are deleted, and never a file this build wrote, so other files
|
|
91
|
+
* in the directory stay.
|
|
92
|
+
*
|
|
93
|
+
* The build calls this after every page is written: until then, pages from the previous
|
|
94
|
+
* build still load the old files.
|
|
95
|
+
*
|
|
96
|
+
* @param options - The project, its TypeScript config and output directory, and the bundles
|
|
97
|
+
* this build compiled
|
|
98
|
+
*/
|
|
99
|
+
export async function removeStaleBundleFiles(options) {
|
|
100
|
+
const { projectRoot, config, outDir: globalOutDir = DEFAULT_OUT_DIR, compiled } = options;
|
|
101
|
+
if (compiled.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const outDir = path.join(projectRoot, globalOutDir, resolveConfig(config, 'production').outDir);
|
|
105
|
+
const current = new Set(compiled.map((bundle) => bundle.filename));
|
|
106
|
+
// name.js, name-HASH.js (esbuild's 8-character hash) and their source maps
|
|
107
|
+
const patterns = compiled.map(({ config }) => {
|
|
108
|
+
const name = config.bundleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
109
|
+
return new RegExp(`^${name}(?:-[A-Z0-9]{8})?\\.js(?:\\.map)?$`);
|
|
110
|
+
});
|
|
111
|
+
const entries = await readdir(outDir, { withFileTypes: true }).catch(() => []);
|
|
112
|
+
// Files only: remove() deletes folders with their contents, and links are left alone
|
|
113
|
+
const stale = entries
|
|
114
|
+
.filter((entry) => entry.isFile())
|
|
115
|
+
.map((entry) => entry.name)
|
|
116
|
+
.filter((file) => !current.has(file) && patterns.some((pattern) => pattern.test(file)));
|
|
117
|
+
await Promise.all(stale.map((file) => remove(path.join(outDir, file))));
|
|
118
|
+
}
|
|
86
119
|
/**
|
|
87
120
|
* Compile a single bundle using esbuild.
|
|
88
121
|
* @internal
|
|
@@ -82,21 +82,32 @@ export declare function generateSearchIndex(searchablePages: SearchablePage[], c
|
|
|
82
82
|
export declare function writeSearchIndex(searchIndex: SearchIndex, outDir: string, filename: string, skipIfUnchanged?: boolean): Promise<SearchIndexMetadata>;
|
|
83
83
|
/**
|
|
84
84
|
* Computes the search index filename based on configuration.
|
|
85
|
-
* When hashFilename is true,
|
|
86
|
-
*
|
|
87
|
-
*
|
|
85
|
+
* When hashFilename is true, the name carries a hash of `hashInput`. Production builds pass
|
|
86
|
+
* the index content, so the name changes only when the indexed text does; every page links to
|
|
87
|
+
* the index, so the name is part of each page's cache hash.
|
|
88
88
|
*
|
|
89
89
|
* @param config - Search configuration
|
|
90
|
-
* @param
|
|
90
|
+
* @param hashInput - The value the hash is computed from, such as the serialized documents
|
|
91
91
|
* @returns The search index filename (without leading slash)
|
|
92
92
|
*
|
|
93
93
|
* @example
|
|
94
94
|
* ```typescript
|
|
95
|
-
*
|
|
96
|
-
* const filename = computeSearchIndexFilename(config.search, Date.now().toString());
|
|
95
|
+
* const filename = computeSearchIndexFilename(config.search, JSON.stringify(index.documents));
|
|
97
96
|
* // filename: 'search-index-a1b2c3d4.json' (when hashFilename is true)
|
|
98
97
|
* // filename: 'search-index.json' (when hashFilename is false)
|
|
99
98
|
* ```
|
|
100
99
|
*/
|
|
101
|
-
export declare function computeSearchIndexFilename(config: SearchConfig,
|
|
100
|
+
export declare function computeSearchIndexFilename(config: SearchConfig, hashInput?: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* Deletes search index files that earlier builds wrote under another name: hashed files from
|
|
103
|
+
* builds whose indexed text differed, and the file of a build with the other `hashFilename`
|
|
104
|
+
* setting. Only files in the output directory named after `indexName` are deleted, never the
|
|
105
|
+
* current one.
|
|
106
|
+
*
|
|
107
|
+
* @param outDir - The output directory
|
|
108
|
+
* @param config - Search configuration
|
|
109
|
+
* @param currentFilename - The index file this build wrote
|
|
110
|
+
* @returns The deleted file names
|
|
111
|
+
*/
|
|
112
|
+
export declare function removeStaleSearchIndexes(outDir: string, config: SearchConfig, currentFilename: string): Promise<string[]>;
|
|
102
113
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/search/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAqBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA2BtE;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8DtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,QAAQ,EAAE,EACf,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACnC,MAAM,EAAE,YAAY,GACnB,cAAc,EAAE,CAiGlB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CA2BhF;AAED;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAM1E;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,oEAAoE;IACpE,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,cAAc,EAAE,EACjC,MAAM,EAAE,YAAY,GACnB,WAAW,CA6Bb;AAiBD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,UAAQ,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAqC9B;AAED
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/search/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAqBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA2BtE;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8DtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,QAAQ,EAAE,EACf,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACnC,MAAM,EAAE,YAAY,GACnB,cAAc,EAAE,CAiGlB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CA2BhF;AAED;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAM1E;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,oEAAoE;IACpE,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,cAAc,EAAE,EACjC,MAAM,EAAE,YAAY,GACnB,WAAW,CA6Bb;AAiBD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,UAAQ,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAqC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAW3F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,EACpB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBnB"}
|
package/dist/search/generator.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { writeFile, ensureDir } from '../core/utils/fs.utils.js';
|
|
7
|
+
import { writeFile, ensureDir, readdir, remove } from '../core/utils/fs.utils.js';
|
|
8
8
|
import { matchesGlob } from '../core/utils/glob-patterns.utils.js';
|
|
9
9
|
import { isVirtualPage } from '../core/utils/template-discovery.utils.js';
|
|
10
10
|
import { SEARCH_INDEX_VERSION, SEARCH_DEFAULTS } from './constants.js';
|
|
@@ -327,32 +327,55 @@ export async function writeSearchIndex(searchIndex, outDir, filename, skipIfUnch
|
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
329
|
* Computes the search index filename based on configuration.
|
|
330
|
-
* When hashFilename is true,
|
|
331
|
-
*
|
|
332
|
-
*
|
|
330
|
+
* When hashFilename is true, the name carries a hash of `hashInput`. Production builds pass
|
|
331
|
+
* the index content, so the name changes only when the indexed text does; every page links to
|
|
332
|
+
* the index, so the name is part of each page's cache hash.
|
|
333
333
|
*
|
|
334
334
|
* @param config - Search configuration
|
|
335
|
-
* @param
|
|
335
|
+
* @param hashInput - The value the hash is computed from, such as the serialized documents
|
|
336
336
|
* @returns The search index filename (without leading slash)
|
|
337
337
|
*
|
|
338
338
|
* @example
|
|
339
339
|
* ```typescript
|
|
340
|
-
*
|
|
341
|
-
* const filename = computeSearchIndexFilename(config.search, Date.now().toString());
|
|
340
|
+
* const filename = computeSearchIndexFilename(config.search, JSON.stringify(index.documents));
|
|
342
341
|
* // filename: 'search-index-a1b2c3d4.json' (when hashFilename is true)
|
|
343
342
|
* // filename: 'search-index.json' (when hashFilename is false)
|
|
344
343
|
* ```
|
|
345
344
|
*/
|
|
346
|
-
export function computeSearchIndexFilename(config,
|
|
345
|
+
export function computeSearchIndexFilename(config, hashInput) {
|
|
347
346
|
const indexName = config.indexName ?? SEARCH_DEFAULTS.indexName;
|
|
348
347
|
const hashFilename = config.hashFilename ?? SEARCH_DEFAULTS.hashFilename;
|
|
349
348
|
if (hashFilename) {
|
|
350
|
-
|
|
351
|
-
// This allows the filename to be known before content is generated
|
|
352
|
-
const hashInput = buildId ?? Date.now().toString();
|
|
353
|
-
const hash = generateContentHash(hashInput);
|
|
349
|
+
const hash = generateContentHash(hashInput ?? Date.now().toString());
|
|
354
350
|
return `${indexName}-${hash}.json`;
|
|
355
351
|
}
|
|
356
352
|
// Return base filename (without hash)
|
|
357
353
|
return `${indexName}.json`;
|
|
358
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Deletes search index files that earlier builds wrote under another name: hashed files from
|
|
357
|
+
* builds whose indexed text differed, and the file of a build with the other `hashFilename`
|
|
358
|
+
* setting. Only files in the output directory named after `indexName` are deleted, never the
|
|
359
|
+
* current one.
|
|
360
|
+
*
|
|
361
|
+
* @param outDir - The output directory
|
|
362
|
+
* @param config - Search configuration
|
|
363
|
+
* @param currentFilename - The index file this build wrote
|
|
364
|
+
* @returns The deleted file names
|
|
365
|
+
*/
|
|
366
|
+
export async function removeStaleSearchIndexes(outDir, config, currentFilename) {
|
|
367
|
+
const indexName = (config.indexName ?? SEARCH_DEFAULTS.indexName).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
368
|
+
// name.json and name-HASH.json (generateContentHash's 8 hex characters)
|
|
369
|
+
const pattern = new RegExp(`^${indexName}(?:-[0-9a-f]{8})?\\.json$`);
|
|
370
|
+
const entries = await readdir(outDir, { withFileTypes: true }).catch(() => null);
|
|
371
|
+
if (!entries) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
// Files only: remove() deletes folders with their contents, and links are left alone
|
|
375
|
+
const stale = entries
|
|
376
|
+
.filter((entry) => entry.isFile())
|
|
377
|
+
.map((entry) => entry.name)
|
|
378
|
+
.filter((file) => file !== currentFilename && pattern.test(file));
|
|
379
|
+
await Promise.all(stale.map((file) => remove(join(outDir, file))));
|
|
380
|
+
return stale;
|
|
381
|
+
}
|
package/dist/search/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* console.log(`Generated search index at ${metadata.indexPath}`);
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export { generateSearchIndex, writeSearchIndex, extractSectionsFromMarkdown, stripMarkdown, shouldExcludePage, isGeneratedPageExcludedFromSearch, generateContentHash, buildBreadcrumb, computeSearchIndexFilename, } from './generator.js';
|
|
19
|
+
export { generateSearchIndex, writeSearchIndex, extractSectionsFromMarkdown, stripMarkdown, shouldExcludePage, isGeneratedPageExcludedFromSearch, generateContentHash, buildBreadcrumb, computeSearchIndexFilename, removeStaleSearchIndexes, } from './generator.js';
|
|
20
20
|
export type { SearchablePage } from './generator.js';
|
|
21
21
|
export { autoInjectSearchMeta } from './auto-inject.js';
|
|
22
22
|
export { SEARCH_INDEX_META_NAME, SEARCH_INDEX_VERSION, SEARCH_DEFAULTS } from './constants.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,iBAAiB,EACjB,iCAAiC,EACjC,mBAAmB,EACnB,eAAe,EACf,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,iBAAiB,EACjB,iCAAiC,EACjC,mBAAmB,EACnB,eAAe,EACf,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE/F,YAAY,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,mBAAmB,GACpB,MAAM,oBAAoB,CAAC"}
|
package/dist/search/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* console.log(`Generated search index at ${metadata.indexPath}`);
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export { generateSearchIndex, writeSearchIndex, extractSectionsFromMarkdown, stripMarkdown, shouldExcludePage, isGeneratedPageExcludedFromSearch, generateContentHash, buildBreadcrumb, computeSearchIndexFilename, } from './generator.js';
|
|
19
|
+
export { generateSearchIndex, writeSearchIndex, extractSectionsFromMarkdown, stripMarkdown, shouldExcludePage, isGeneratedPageExcludedFromSearch, generateContentHash, buildBreadcrumb, computeSearchIndexFilename, removeStaleSearchIndexes, } from './generator.js';
|
|
20
20
|
// Auto-injection
|
|
21
21
|
export { autoInjectSearchMeta } from './auto-inject.js';
|
|
22
22
|
export { SEARCH_INDEX_META_NAME, SEARCH_INDEX_VERSION, SEARCH_DEFAULTS } from './constants.js';
|
package/dist/types/config.d.ts
CHANGED
|
@@ -212,16 +212,18 @@ export interface BundleConfig {
|
|
|
212
212
|
bundleName: string;
|
|
213
213
|
/**
|
|
214
214
|
* Glob patterns for pages that should include this bundle.
|
|
215
|
-
* Matches against page
|
|
215
|
+
* Matches against the page URL (e.g., '/docs/api/hooks', or '/' for the home page),
|
|
216
|
+
* not its output file. A pattern must match the whole URL: '/docs/**' does not match
|
|
217
|
+
* the '/docs' index page itself.
|
|
216
218
|
* If omitted, bundle is included on ALL pages (global bundle).
|
|
217
219
|
*
|
|
218
|
-
* Supports glob syntax: `*`, `**`, `?`, `[abc]
|
|
220
|
+
* Supports glob syntax: `*`, `**`, `?`, `[abc]` (no `{a,b}`; list each pattern instead)
|
|
219
221
|
*
|
|
220
|
-
* @example ['/docs/**', '/api/**']
|
|
222
|
+
* @example ['/docs', '/docs/**', '/api/**']
|
|
221
223
|
*/
|
|
222
224
|
include?: string[];
|
|
223
225
|
/**
|
|
224
|
-
* Glob patterns for
|
|
226
|
+
* Glob patterns for page URLs to exclude from this bundle.
|
|
225
227
|
* Takes precedence over include patterns.
|
|
226
228
|
*
|
|
227
229
|
* @example ['/docs/legacy/**']
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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"}
|