diff --git a/frontend/purestack.studio/components/site/nav-menu/nav-menu-playground.ts b/frontend/purestack.studio/components/site/nav-menu/nav-menu-playground.ts index ce47a1f0..129546a5 100644 --- a/frontend/purestack.studio/components/site/nav-menu/nav-menu-playground.ts +++ b/frontend/purestack.studio/components/site/nav-menu/nav-menu-playground.ts @@ -89,7 +89,11 @@ const TREES: Record = { title: 'Guides', icon: 'tabler:book', children: [ - { title: 'Themes', url: '/guides/styling/themes/', icon: 'tabler:palette' }, + { + title: 'Themes', + url: '/guides/styling/themes/', + icon: 'tabler:palette', + }, { title: 'Components', icon: 'tabler:components', diff --git a/frontend/purestack.studio/guides/extending/_nav.json b/frontend/purestack.studio/guides/extending/_nav.json index 56e658fb..7d346cc4 100644 --- a/frontend/purestack.studio/guides/extending/_nav.json +++ b/frontend/purestack.studio/guides/extending/_nav.json @@ -1,7 +1,3 @@ { - "sequence": [ - "index.mdx", - "regor.mdx", - "plugins.mdx" - ] + "sequence": ["index.mdx", "regor.mdx", "plugins.mdx"] } diff --git a/frontend/purestack.studio/guides/getting-started/purestack-cli.mdx b/frontend/purestack.studio/guides/getting-started/purestack-cli.mdx index 91a8188c..a7b8ee5e 100644 --- a/frontend/purestack.studio/guides/getting-started/purestack-cli.mdx +++ b/frontend/purestack.studio/guides/getting-started/purestack-cli.mdx @@ -66,7 +66,7 @@ Add `content/about.mdx` with its own title and body. Then start the preview serv yarn purestack serve --content ./content ``` -Open the URL printed as `serving at` after the first build. With the default settings it is `http://127.0.0.1:4173/`. The server binds to `0.0.0.0` by default; pass `--host 127.0.0.1` if it should listen only on your machine. If `basePath` is set in `siteConfig.json`, the printed URL includes it. +Open the URL printed as `serving at` once shared setup finishes. Pages render as you open them. With the default settings the URL is `http://127.0.0.1:4173/`. The server binds to `0.0.0.0` by default; pass `--host 127.0.0.1` if it should listen only on your machine. If `basePath` is set in `siteConfig.json`, the printed URL includes it. ## Understand the content folder @@ -97,7 +97,7 @@ Every build command requires `--content `. The path is resolved from the wo | Command | Result | Output folder | | --- | --- | --- | | `build` | Generates the complete static site. | `outDir` | -| `serve` | Builds the site, starts an HTTP server, and watches content by default. | `outDir` | +| `serve` | Starts an HTTP server, renders pages on request, and watches content by default. | `outDir` | | `publish` | Cleans and builds a release artifact. | `publishDir` | Use `yarn purestack --help` or `yarn purestack help` to print the CLI's own usage summary. Options can be written as `--content ./content` or `--content=./content`. @@ -118,9 +118,14 @@ yarn purestack build --content ./content --clean ```sh yarn purestack serve --content ./content yarn purestack serve --content ./content --host 127.0.0.1 --port 4300 +yarn purestack serve --content ./content --clean --full-render ``` -The server uses port `4173` and host `0.0.0.0` unless you override them. It serves files from `outDir`, checks watched content changes, and reloads open pages after changes. Content changes are handled incrementally where possible; a change to `siteConfig.json`, to `purestack.config.ts`, or to a file the config imports causes a full rebuild so the new settings and plugins are applied. +The server uses port `4173` and host `0.0.0.0` unless you override them. By default, startup prepares routes, navigation, and shared page sections. Each page renders on its first request and is cached for later requests; static assets copy when requested. Watched edits invalidate affected pages so they render again on the next request, and open pages reload automatically. Changes to `siteConfig.json`, `purestack.config.ts`, or a file the config imports refresh shared setup and invalidate cached pages. + +Use `--full-render` when you need every page, asset, and the complete search index generated before browsing. It runs one full build at startup, and site requests wait for that build to finish. After startup, watched edits use the same incremental invalidation and rendering on request as the default mode. + +When search is enabled, the default mode builds its search index when a Pagefind asset is requested and includes only pages opened in the current session. `--full-render` starts with a complete search index; later changes are handled incrementally. `build` and `publish` always index the complete site. These options belong to `serve`: @@ -128,9 +133,10 @@ These options belong to `serve`: | --- | --- | | `--host ` | Listen on a particular interface, for example `127.0.0.1`. | | `--port ` | Listen on a different port. | -| `--clean` | Remove `outDir` before the initial build. | -| `--no-watch` | Keep the server running without watching the content directory. The initial build still runs. | -| `--no-reload` | Keep watching and rebuilding, but do not inject the browser live reload script. | +| `--clean` | Remove `outDir` before setup and when config changes reset shared setup. | +| `--full-render` | Build every page, asset, and the complete search index once at startup. Watched edits remain incremental. | +| `--no-watch` | Keep the server running without watching the content directory. Pages still render on request, or all at startup with `--full-render`. | +| `--no-reload` | Keep watching and updating the site, but do not inject the browser live reload script. | Watching and browser reload are separate. For example, `--no-reload` is useful if another browser tool manages refreshes. The server displays page build errors as diagnostic pages so you can correct the source and continue editing. @@ -169,10 +175,8 @@ The home page stays at `dist/site/index.html`, but links and the development ser | --- | --- | | `Missing required --content option` | Pass `--content` to `build`, `serve`, or `publish`. | | `Missing required siteConfig.json` | Check the path passed to `--content` and the file's exact name. | -| `Unknown option` | Run `yarn purestack --help`; `--host`, `--port`, `--no-watch`, and `--no-reload` apply only to `serve`. | +| `Unknown option` | Run `yarn purestack --help`; `--host`, `--port`, `--no-watch`, `--no-reload`, and `--full-render` apply only to `serve`. | | `Could not bundle …purestack.config.ts` | Check the imports in `purestack.config.ts` and the files it imports; the message names the import that failed. | | `Duplicate content routes detected` | Find two files that map to the same URL, such as `guide/index.md` and `guide/guide.mdx`. | | A page or asset returns 404 under a subpath | Check `basePath`, the URL printed by `serve`, and the path at which your host serves the output folder. | | `publish` fails while `serve` displayed an error page | Fix the page or asset error shown by the development server, then rerun `publish`. | - -The checked-in [sample content](https://github.com/PureStackStudio/PureStack/tree/main/packages/ts-ssg/sample-content) is a larger working site. PureStack Studio adds its own skin, components, styles, and templates through a [plugin](https://github.com/PureStackStudio/PureStack/blob/main/frontend/studioPlugin.ts) listed in its [`purestack.config.ts`](https://github.com/PureStackStudio/PureStack/blob/main/frontend/purestack.studio/purestack.config.ts); `yarn frontend` runs `purestack serve` on that site. diff --git a/frontend/purestack.studio/guides/publishing/_nav.json b/frontend/purestack.studio/guides/publishing/_nav.json index 4639a034..071e03a4 100644 --- a/frontend/purestack.studio/guides/publishing/_nav.json +++ b/frontend/purestack.studio/guides/publishing/_nav.json @@ -1,7 +1,3 @@ { - "sequence": [ - "index.mdx", - "search-and-metadata.mdx", - "deployment.mdx" - ] + "sequence": ["index.mdx", "search-and-metadata.mdx", "deployment.mdx"] } diff --git a/package.json b/package.json index 72dc8397..1e031ed5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "purestack-repo", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.18.1", "type": "module", "sideEffects": false, @@ -25,9 +25,8 @@ "tsx": "^4.23.15", "typescript": "7.0.2", "typescript-parser": "npm:typescript@6.0.3", - "vite": "^8.3.1", - "vite-tsconfig-paths": "^6.1.1", - "vitest": "^4.0.18" + "vite": "^8.3.2", + "vitest": "^5.0.3" }, "files": [], "scripts": { diff --git a/packages/purestack/package.json b/packages/purestack/package.json index cca624d1..f0831c32 100644 --- a/packages/purestack/package.json +++ b/packages/purestack/package.json @@ -1,6 +1,6 @@ { "name": "purestack", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/purestack.mjs", "types": "./dist/purestack.d.mts", diff --git a/packages/purestack/src/index.ts b/packages/purestack/src/index.ts index 36f8b6fe..8dfc4a2e 100644 --- a/packages/purestack/src/index.ts +++ b/packages/purestack/src/index.ts @@ -1,3 +1,3 @@ -export const version: string = '1.1.3' +export const version: string = '1.1.4' export * from '@purestack/ts-ssg' diff --git a/packages/ts-common/package.json b/packages/ts-common/package.json index 350e3de8..de08f5f8 100644 --- a/packages/ts-common/package.json +++ b/packages/ts-common/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-common", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-common.mjs", "types": "./dist/ts-common.d.mts", diff --git a/packages/ts-components/package.json b/packages/ts-components/package.json index c1f1747d..0a4088f2 100644 --- a/packages/ts-components/package.json +++ b/packages/ts-components/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-components", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-components.mjs", "types": "./dist/ts-components.d.mts", diff --git a/packages/ts-css/package.json b/packages/ts-css/package.json index 7d5e4662..6d5d5e25 100644 --- a/packages/ts-css/package.json +++ b/packages/ts-css/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-css", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-css.mjs", "types": "./dist/ts-css.d.mts", diff --git a/packages/ts-html/package.json b/packages/ts-html/package.json index f2de880c..2b0108cd 100644 --- a/packages/ts-html/package.json +++ b/packages/ts-html/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-html", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-html.mjs", "types": "./dist/ts-html.d.mts", diff --git a/packages/ts-minidom/package.json b/packages/ts-minidom/package.json index d3fdd820..383240da 100644 --- a/packages/ts-minidom/package.json +++ b/packages/ts-minidom/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-minidom", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-minidom.mjs", "types": "./dist/ts-minidom.d.mts", diff --git a/packages/ts-page-scripts/package.json b/packages/ts-page-scripts/package.json index 06987bf2..e079f541 100644 --- a/packages/ts-page-scripts/package.json +++ b/packages/ts-page-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-page-scripts", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-page-scripts.mjs", "types": "./dist/ts-page-scripts.d.mts", diff --git a/packages/ts-render/package.json b/packages/ts-render/package.json index 7e79981b..14f58032 100644 --- a/packages/ts-render/package.json +++ b/packages/ts-render/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-render", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-render.mjs", "types": "./dist/ts-render.d.mts", diff --git a/packages/ts-ssg-vscode/package.json b/packages/ts-ssg-vscode/package.json index c8a25bd0..a569d0c9 100644 --- a/packages/ts-ssg-vscode/package.json +++ b/packages/ts-ssg-vscode/package.json @@ -2,7 +2,7 @@ "name": "purestack-component-navigation", "displayName": "PureStack Component Tools", "description": "Component navigation, prop IntelliSense, lit-html syntax highlighting, and template formatting for Markdown, Regor MDX, and TypeScript templates.", - "version": "1.1.3", + "version": "1.1.4", "private": true, "publisher": "purestack", "license": "MIT", diff --git a/packages/ts-ssg/README.md b/packages/ts-ssg/README.md index b7efcb34..4ce7a73f 100644 --- a/packages/ts-ssg/README.md +++ b/packages/ts-ssg/README.md @@ -72,6 +72,7 @@ Flags: - `--host 127.0.0.1` or `--host=127.0.0.1` (`serve`) - `--no-watch` (`serve`) - `--no-reload` (`serve`) +- `--full-render` (`serve`; render all pages and build the complete search index once at startup) When the content directory holds a `purestack.config.ts`, every command loads its plugins; `serve` reloads it when it or a local file it imports changes. See [Plugins](#plugins). @@ -80,6 +81,7 @@ Examples from this monorepo: ```bash yarn tsx packages/ts-ssg/src/cli.ts build --content ./packages/ts-ssg/sample-content yarn tsx packages/ts-ssg/src/cli.ts serve --content ./packages/ts-ssg/sample-content --port 4173 +yarn frontend --clean --full-render yarn tsx packages/ts-ssg/src/cli.ts publish --content ./packages/ts-ssg/sample-content ``` @@ -385,7 +387,7 @@ Each full build also runs, in order: - `onStylesWritten(context, result)` - `onBuildComplete(context, result)` -The dev server runs a full build when it starts, when `siteConfig.json` changes, and when `purestack.config.ts` or a file it imports changes; other edits re-render only the affected pages. +By default, the dev server prepares routes and shared navigation when it starts. Pages render on their first request and are cached until an edit affects them; static assets copy on request. Site or plugin config changes prepare a fresh request cache. Page hooks run only for pages that are requested; `onBuildComplete` belongs to full builds. With `--full-render` (or `startDevServer({ fullRender: true })`), startup runs one full build, including all pages, assets, and the complete search index. Site requests wait for that initial build to finish. After startup, watched changes use the same incremental invalidation and rendering on request as the default mode. ## Templates @@ -457,10 +459,10 @@ In dev/watch mode: - file changes apply incrementally when safe, - a page affected by a shared change, such as a header, footer, or navigation edit, renders again on its next request, before it is served, -- site config changes trigger full rebuild, -- a change to `purestack.config.ts` or a local file it imports loads the config again and triggers a full rebuild, +- site config changes refresh shared state and invalidate cached pages, +- a change to `purestack.config.ts` or a local file it imports loads the config again and invalidates cached pages, - plugin generated pages regenerate when content or assets change, -- lazy route render can happen on first request for missing HTML route, +- pages render only when requested, including after file edits, - live reload is served over SSE (`/__ts-ssg/events`). - dev server enables `writeErrorPages` automatically so template/MDX errors are visible immediately at the failing route. @@ -479,6 +481,8 @@ Optional config: When `basePath` is configured, the search runtime loads Pagefind from the public mount path while Pagefind indexing still reads the normal output directory. +In the default dev server mode, requesting a Pagefind asset builds the search index from pages rendered in the current session. Unopened pages are not compiled for search. Full builds index the complete site; `serve --full-render` starts with that complete index, then handles watched changes incrementally. + When disabled, stale `/pagefind` output is removed. Build logs include indexed page count and total indexed byte size when indexing runs. ### Sitemap / Robots diff --git a/packages/ts-ssg/package.json b/packages/ts-ssg/package.json index 059d7e5b..88d697b6 100644 --- a/packages/ts-ssg/package.json +++ b/packages/ts-ssg/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-ssg", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-ssg.mjs", "types": "./dist/ts-ssg.d.mts", diff --git a/packages/ts-ssg/src/build/incremental/change-applier.ts b/packages/ts-ssg/src/build/incremental/change-applier.ts index 05e8d651..a9ef864f 100644 --- a/packages/ts-ssg/src/build/incremental/change-applier.ts +++ b/packages/ts-ssg/src/build/incremental/change-applier.ts @@ -32,6 +32,7 @@ interface IncrementalChangeApplierInput { contentState: IncrementalContentState scriptEntrypoints: ScriptEntrypointManager persistManifest: () => Promise + renderOnRequest: () => boolean } export class IncrementalChangeApplier { @@ -56,7 +57,10 @@ export class IncrementalChangeApplier { ) { const state = await this.readChangeState(filePath, relPath, result) await this.applyChangeState(state) - if (isContentFile(relPath, state.ext) || state.ext === '.ts') { + if ( + !this.input.renderOnRequest() && + (isContentFile(relPath, state.ext) || state.ext === '.ts') + ) { await this.input.scriptEntrypoints.syncState({ result, persist: true, @@ -178,6 +182,16 @@ export class IncrementalChangeApplier { assetEntry: AssetManifestEntry | undefined }) { const { relPath, ext, result, contentEntry, assetEntry } = input + if (this.input.renderOnRequest() && isContentFile(relPath, ext)) { + await this.input.contentState.refreshNavigation() + await this.input.contentState.removeContentEntryForDeletedSource( + relPath, + result, + ) + this.input.scriptEntrypoints.removePage(relPath) + await this.input.persistManifest() + return + } if (contentEntry && this.input.config.navigation.mode !== 'none') { await this.rebuildNavigationForChange(relPath, ext, result, null) this.input.scriptEntrypoints.removePage(relPath) @@ -215,6 +229,17 @@ export class IncrementalChangeApplier { result: IncrementalBuildResult }) { const { relPath, ext, signature, contentEntry, result } = input + if (this.input.renderOnRequest()) { + // A watcher event is an invalidation even if a full startup build + // recorded the new signature after rendering the old source. + if (this.input.config.navigation.mode !== 'none') { + await this.input.contentState.refreshNavigation() + } else { + await this.input.contentState.refreshContent() + } + this.input.contentState.markPagesDirty([relPath]) + return + } if (signatureEqual(contentEntry, signature)) return if (this.input.config.navigation.mode !== 'none') { @@ -241,7 +266,8 @@ export class IncrementalChangeApplier { result: IncrementalBuildResult }) { const { relPath, ext, signature, assetEntry, result } = input - if (signatureEqual(assetEntry, signature)) return + if (!this.input.renderOnRequest() && signatureEqual(assetEntry, signature)) + return if (ext === '.ts') { await this.handleScriptAssetChange(relPath, result) @@ -277,6 +303,16 @@ export class IncrementalChangeApplier { this.input.scriptEntrypoints.resolveImpactedEntryRelPaths(relPath) if (impactedEntries.size === 0) return + if (this.input.renderOnRequest()) { + const pages = + this.input.scriptEntrypoints.getPageRelPathsForEntrypoints( + impactedEntries, + ) + this.input.scriptEntrypoints.invalidateEntrypoints(impactedEntries) + this.input.contentState.markPagesDirty(pages) + return + } + const rebuiltEntries = await this.input.scriptEntrypoints.rebuildEntrypoints( impactedEntries, diff --git a/packages/ts-ssg/src/build/incremental/content-state.ts b/packages/ts-ssg/src/build/incremental/content-state.ts index 4b74eba2..be70b463 100644 --- a/packages/ts-ssg/src/build/incremental/content-state.ts +++ b/packages/ts-ssg/src/build/incremental/content-state.ts @@ -43,6 +43,7 @@ interface IncrementalContentStateInput { log: Logger onPageBuilt: (relPath: string, scriptEntrypoints: string[]) => void onPageRemoved: (relPath: string) => void + renderOnRequest: () => boolean persistManifest: () => Promise getManifest: () => BuildManifest } @@ -63,7 +64,8 @@ interface RebuildSingleContentInput { } export class IncrementalContentState { - private readonly dirtyPages = new Set() + private readonly dirtyPages = new Map() + private dirtyVersion = 0 private markedPages = 0 private generatedPages = new Map() /** The files each page imports, by page. */ @@ -120,7 +122,6 @@ export class IncrementalContentState { try { await hooks.onPageStart?.(context, file) const page = await renderPageFromFile(context, file, hooks) - this.input.onPageBuilt(file.relPath, page.scriptEntrypoints) this.importedFilesByPage.set(file.relPath, new Set(page.importedFiles)) if (page.frontmatter.index === false) { this.unindexedPages.set(file.relPath, page.outPath) @@ -130,6 +131,7 @@ export class IncrementalContentState { await hooks.onPageRendered?.(context, page) await writePage(page, config.html.minify) await hooks.onPageWritten?.(context, page) + this.input.onPageBuilt(file.relPath, page.scriptEntrypoints) return true } catch (error) { if (context.writeErrorPages !== true) throw error @@ -149,9 +151,25 @@ export class IncrementalContentState { return this.renderPageByRelPath(relPath, true) } - async renderByUrlPath(urlPath: string) { + async renderByUrlPath(urlPath: string, locale?: string) { const normalized = normalizeUrlPath(urlPath) - let relPath = this.contentIndex.getRelPathByUrlPath(normalized) + const outputRelPath = urlPath.endsWith('/index.html') + ? this.contentIndex.getRelPathByOutPath( + path.resolve(this.input.config.outDir, `.${urlPath}`), + ) + : undefined + let relPath = + outputRelPath ?? this.contentIndex.getRelPathByUrlPath(normalized) + if ( + !outputRelPath && + locale && + this.input.config.i18n.urlStrategy === 'hidden' + ) { + relPath = + this.input.context.contentRoutes.pages.find( + (file) => file.urlPath === normalized && file.locale === locale, + )?.relPath ?? relPath + } if (!relPath) { const contentFiles = await this.refreshContent() this.contentIndex.updateUrlPathMapFromFiles(contentFiles) @@ -175,6 +193,7 @@ export class IncrementalContentState { this.input.onPageRemoved(relPath) } this.updateContentRoutes(context.contentRoutes.withPages(contentFiles)) + this.indexContentFiles(contentFiles) this.markPagesDirty(generated.changed) context.translationsByKey = buildTranslationsByKey(contentFiles) return contentFiles @@ -185,7 +204,14 @@ export class IncrementalContentState { * before the build that discovered it writes the manifest. */ indexContentFiles(contentFiles: ResolvedContentFile[]) { + this.contentIndex.clear() this.contentIndex.updateUrlPathMapFromFiles(contentFiles) + for (const file of contentFiles) { + this.contentIndex.setOutPath( + file.relPath, + resolveOutPath(this.input.config.outDir, file), + ) + } } /** Generators may read data files, so an asset change runs them again. */ @@ -220,11 +246,9 @@ export class IncrementalContentState { } /** Picks up added or removed assets from the manifest. */ - refreshAssets() { - const { context, getManifest } = this.input - this.updateContentRoutes( - context.contentRoutes.withAssets(Object.keys(getManifest().assets)), - ) + refreshAssets(assetRelPaths = Object.keys(this.input.getManifest().assets)) { + const { context } = this.input + this.updateContentRoutes(context.contentRoutes.withAssets(assetRelPaths)) } private updateContentRoutes(contentRoutes: ContentRouteIndex) { @@ -304,6 +328,7 @@ export class IncrementalContentState { this.dirtyPages.delete(relPath) this.importedFilesByPage.delete(relPath) this.unindexedPages.delete(relPath) + this.input.onPageRemoved(relPath) result.deletedPages += 1 } @@ -327,11 +352,15 @@ export class IncrementalContentState { } async renderAndPersistRelPath(relPath: string, signature: FileSignature) { + const dirtyVersion = this.dirtyPages.get(relPath) const ext = this.resolveContentExt(relPath) const contentFile = this.toResolvedContentFile(relPath, ext) await this.writePageWithHooks(contentFile) this.upsertContentManifestEntry(relPath, contentFile.ext, signature) - this.dirtyPages.delete(relPath) + // An edit received while this render was running still needs a new render. + if (this.dirtyPages.get(relPath) === dirtyVersion) { + this.dirtyPages.delete(relPath) + } await this.input.persistManifest() return true } @@ -358,6 +387,7 @@ export class IncrementalContentState { await removeFile(entry.outPath) delete this.input.getManifest().content[relPath] this.contentIndex.remove(relPath) + this.input.onPageRemoved(relPath) await this.input.persistManifest() } this.dirtyPages.delete(relPath) @@ -409,17 +439,25 @@ export class IncrementalContentState { ) } - private markAllPagesDirty(contentFiles: readonly ResolvedContentFile[]) { + markAllPagesDirty(contentFiles: readonly ResolvedContentFile[]) { this.dirtyPages.clear() this.markPagesDirty(contentFiles.map((file) => file.relPath)) } - private markPagesDirty(relPaths: readonly string[]) { - for (const relPath of relPaths) this.dirtyPages.add(relPath) + markPagesDirty(relPaths: readonly string[]) { + for (const relPath of relPaths) + this.dirtyPages.set(relPath, ++this.dirtyVersion) this.markedPages += relPaths.length } private async renderPageByRelPath(relPath: string, onlyIfDirty: boolean) { + if ( + this.input.renderOnRequest() && + this.input.getManifest().content[relPath] && + !this.dirtyPages.has(relPath) + ) { + return true + } if (onlyIfDirty && !this.dirtyPages.has(relPath)) return false const inFlight = this.renderInFlight.get(relPath) if (inFlight) return inFlight @@ -430,7 +468,7 @@ export class IncrementalContentState { if (!signature) { return this.handleMissingRelPathSource(relPath) } - return this.renderAndPersistRelPath(relPath, signature) + return await this.renderAndPersistRelPath(relPath, signature) } catch (error) { this.input.log.error('incremental render failed', { relPath, diff --git a/packages/ts-ssg/src/build/incremental/incremental.md b/packages/ts-ssg/src/build/incremental/incremental.md index 4168535d..bac5d836 100644 --- a/packages/ts-ssg/src/build/incremental/incremental.md +++ b/packages/ts-ssg/src/build/incremental/incremental.md @@ -32,8 +32,11 @@ reason about “what changed?” vs “what must be rebuilt?”. 2. It loads the existing manifest, keeping it only if compatible with current config (`isCompatibleManifest`); otherwise it starts from `createEmptyManifest`. -3. It returns `{ buildAll, applyChange, renderIfDirtyByOutPath, - renderByUrlPath }` for the caller to use in dev servers or watch mode. +3. It returns full-build and request-rendering methods. The default dev server + calls `prepareForRequests`; build commands and `serve --full-render` call + `buildAll`. After the startup build, `serve --full-render` switches to + request rendering and reuses its pages, asset records, and search index. + Watched changes then use the same invalidation as the default dev mode. The content is prepared once per full build. `buildAll` prepares it as part of the build. `applyChange` and the two render calls first await @@ -42,6 +45,16 @@ has run, as when resuming from an earlier build's manifest, prepare the content from the manifest themselves. A failed preparation is tried again by the next call. +`prepareForRequests` prepares config hooks, the output directory, routes, +generated page definitions, navigation, and shared partials. It renders no +pages and copies no static assets. Existing output is treated as stale in the +new session, even without `--clean`. Requests share any in-flight render and +reuse clean pages afterward. Before serving HTML, `preparePageAssets` builds +missing scripts for rendered pages and writes their shared styles. Static +assets copy through `prepareAssetByUrlPath` when requested. Pagefind assets +trigger indexing of pages rendered in the current session, excluding stale +HTML from earlier sessions. No background loop renders the rest of the site. + ## `buildAll(reason)` This is the canonical full build path: @@ -62,10 +75,11 @@ The output is deterministic with `generatedAt` being the only volatile field. ## `applyChange(filePath)` -Handles a single filesystem change event. The changed page renders right -away; other affected pages are marked dirty and render on their next request -(`renderIfDirtyByOutPath`). `IncrementalBuildResult.markedPages` counts them, -so the dev server can reload the browser. +Handles a single filesystem change event. In request mode, affected pages +become dirty and render on their next request; edited script bundles and static +assets are rebuilt/copied when requested too. In full-build mode, the changed +page renders right away. `IncrementalBuildResult.markedPages` counts pages +invalidated by the change, so the dev server can reload the browser. 1. **Ignore changes outside `contentDir`**: relative path starting with `..`. 2. **`siteConfig.json`** ➜ `fullRebuild = true`. Config shapes every output. diff --git a/packages/ts-ssg/src/build/incremental/index.ts b/packages/ts-ssg/src/build/incremental/index.ts index 48ab605b..072ab899 100644 --- a/packages/ts-ssg/src/build/incremental/index.ts +++ b/packages/ts-ssg/src/build/incremental/index.ts @@ -1,9 +1,12 @@ import path from 'node:path' import type { SiteConfig } from '@purestack/ts-common' import { themes } from '@purestack/ts-style' -import { toOutputAssetRelPath } from '@purestack/ts-util' +import { toOutputAssetRelPath, toPosixPath } from '@purestack/ts-util' import { getLogger, type Logger } from 'logpot' import { + DEFAULT_NAV_FILENAME, + discoverStaticAssets, + isContentFile, isDefaultFooterFile, isDefaultHeaderFile, isSharedContentFile, @@ -25,7 +28,7 @@ import { } from '../../plugins/plugin' import { initBuiltinComponents } from '../../regor/initBuiltinComponents' import { resolveRouteInfo } from '../../routing/route' -import { copyStaticAssets } from '../assets' +import { copyStaticAsset, copyStaticAssets } from '../assets' import { resolveBuildSiteConfig } from '../build-config' import { ContentRouteIndex } from '../content-urls' import { writeGeneratedFavicon } from '../favicon' @@ -35,6 +38,7 @@ import { createEmptyManifest, isCompatibleManifest, readManifest, + readSignature, writeManifest, } from '../manifest' import { type BuildContext, resolveHeaderFooterHtml } from '../page' @@ -51,6 +55,7 @@ import { countByExt, discoverSiteContent, isOutsideContentRoot, + removeFile, resolveMdxBuildOptions, toContentFile, } from './support' @@ -165,6 +170,13 @@ class IncrementalRuntime { private readonly scriptCacheKeys: ScriptCacheKeyStore /** Settles once the content is prepared; see ensureContentReady. */ private contentReady: Promise | undefined + private assetWork: Promise = Promise.resolve() + private pageAssetRevision = 0 + private preparedAssetRevision = -1 + private renderOnRequest = false + private staticAssets = new Map() + private searchRevision = -1 + private manifestWork: Promise = Promise.resolve() constructor(private readonly options: IncrementalRuntimeOptions) { this.scriptCacheKeys = options.scriptCacheKeys @@ -176,7 +188,7 @@ class IncrementalRuntime { minifyScripts: options.minifyScripts, failOnAssetError: options.failOnAssetError, cacheBusting: options.config.scripts.cacheBusting, - assets: options.manifest.assets, + getAssets: () => this.manifest.assets, scriptCacheKeys: this.scriptCacheKeys, persistManifest: () => this.persistManifest(), }) @@ -186,9 +198,15 @@ class IncrementalRuntime { hooks: options.hooks, plugins: options.plugins, log: options.log, - onPageBuilt: (relPath, scriptEntrypoints) => - this.scriptEntrypoints.setPageEntrypoints(relPath, scriptEntrypoints), - onPageRemoved: (relPath) => this.scriptEntrypoints.removePage(relPath), + onPageBuilt: (relPath, scriptEntrypoints) => { + this.scriptEntrypoints.setPageEntrypoints(relPath, scriptEntrypoints) + this.pageAssetRevision += 1 + }, + onPageRemoved: (relPath) => { + this.scriptEntrypoints.removePage(relPath) + this.pageAssetRevision += 1 + }, + renderOnRequest: () => this.renderOnRequest, persistManifest: () => this.persistManifest(), getManifest: () => this.manifest, }) @@ -198,16 +216,20 @@ class IncrementalRuntime { getManifest: () => this.manifest, contentState: this.contentState, scriptEntrypoints: this.scriptEntrypoints, + renderOnRequest: () => this.renderOnRequest, persistManifest: () => this.persistManifest(), }) } toBuilder(): IncrementalBuilder { return { + prepareForRequests: this.prepareForRequests, buildAll: this.buildAll, applyChange: this.applyChange, renderIfDirtyByOutPath: this.renderIfDirtyByOutPath, renderByUrlPath: this.renderByUrlPath, + preparePageAssets: this.preparePageAssets, + prepareAssetByUrlPath: this.prepareAssetByUrlPath, } } @@ -231,6 +253,44 @@ class IncrementalRuntime { this.options.manifest = value } + prepareForRequests = async (): Promise => { + const switchingFromBuild = + !this.renderOnRequest && this.contentReady !== undefined + this.renderOnRequest = true + this.contentReady ??= this.settleContentReady(this.prepareRequestState()) + await this.contentReady + if (switchingFromBuild) await this.discoverRequestAssets() + } + + private async prepareRequestState() { + await this.options.hooks.onConfigResolved?.(this.context) + await prepareOutDir(this.config.outDir, { clean: this.options.cleanOutDir }) + // Keep the records object shared with the script manager, but start this + // session with no rendered pages or copied assets, even without --clean. + this.manifest.content = {} + for (const key of Object.keys(this.manifest.assets)) + delete this.manifest.assets[key] + this.scriptCacheKeys.clear() + await writeGeneratedFavicon(this.config) + await this.discoverRequestAssets() + const files = await this.prepareContent( + [...this.staticAssets.values()].map((file) => file.relPath), + this.options.hooks, + ) + this.contentState.markAllPagesDirty(files) + this.contentState.takeMarkedPageCount() + } + + private async discoverRequestAssets() { + const files = await discoverStaticAssets(this.config.contentDir) + this.staticAssets = new Map( + files.map((file) => [ + `/${toPosixPath(toOutputAssetRelPath(file.relPath))}`, + file, + ]), + ) + } + buildAll = async (reason: string): Promise => { const buildStartMs = Date.now() const hooks = this.resolveBuildHooks() @@ -248,6 +308,7 @@ class IncrementalRuntime { rebuildAll: true, }) const styleResult = await this.writeStylesWithHooks(hooks) + this.preparedAssetRevision = this.pageAssetRevision const finalResult = await this.finalizeBuild({ ...prepared, assetFiles: [...prepared.assetFiles, ...scriptAssetFiles], @@ -330,7 +391,9 @@ class IncrementalRuntime { */ private ensureContentReady(): Promise { this.contentReady ??= this.settleContentReady( - this.prepareContent(Object.keys(this.manifest.assets)), + this.renderOnRequest + ? this.prepareRequestState() + : this.prepareContent(Object.keys(this.manifest.assets)), ) return this.contentReady } @@ -385,6 +448,7 @@ class IncrementalRuntime { this.config.pagefind, this.contentState.unindexedOutPaths(), ) + this.searchRevision = this.pageAssetRevision this.manifest = await buildManifest( this.config, @@ -419,10 +483,31 @@ class IncrementalRuntime { await this.ensureContentReady() this.contentState.takeMarkedPageCount() - if (isDefaultHeaderFile(relPath) || isDefaultFooterFile(relPath)) { + const ext = path.extname(relPath).toLowerCase() + if ( + this.renderOnRequest && + !isContentFile(relPath, ext) && + !isSharedContentFile(relPath) && + !isDefaultHeaderFile(relPath) && + !isDefaultFooterFile(relPath) && + ext !== '.ts' && + path.basename(relPath).toUpperCase() !== + DEFAULT_NAV_FILENAME.toUpperCase() + ) { + await removeFile( + path.join(this.config.outDir, toOutputAssetRelPath(relPath)), + ) + delete this.manifest.assets[relPath] + await this.discoverRequestAssets() + this.contentState.refreshAssets( + [...this.staticAssets.values()].map((file) => file.relPath), + ) + await this.contentState.refreshGeneratedPages() + result.changedAssets += 1 + } else if (isDefaultHeaderFile(relPath) || isDefaultFooterFile(relPath)) { await this.contentState.refreshPartials() - } else if (!isSharedContentFile(relPath)) { - // Shared content is never a page; its importers refresh below. + } else if (!isSharedContentFile(relPath) || ext === '.ts') { + // Shared TypeScript can be a script dependency as well as an import. await this.changeApplier.applyFileChange(filePath, relPath, result) } await this.contentState.refreshImporters(relPath) @@ -435,12 +520,96 @@ class IncrementalRuntime { return this.contentState.renderIfDirtyByOutPath(outPath) } - renderByUrlPath = async (urlPath: string): Promise => { + renderByUrlPath = async ( + urlPath: string, + locale?: string, + ): Promise => { + await this.ensureContentReady() + return this.contentState.renderByUrlPath(urlPath, locale) + } + + preparePageAssets = async (): Promise => { await this.ensureContentReady() - return this.contentState.renderByUrlPath(urlPath) + await this.queueAssetWork(async () => { + const revision = this.pageAssetRevision + if (revision === this.preparedAssetRevision) return + // Only pages rendered in this session have registered script entries. + await this.scriptEntrypoints.buildMissingEntrypoints( + this.changeApplier.createResult('requested page assets'), + ) + const styles = await this.writeStylesWithHooks(this.options.hooks) + this.manifest.styles = { + signature: styles.signature, + outputs: styles.outputs, + } + this.preparedAssetRevision = revision + }) + } + + prepareAssetByUrlPath = async (urlPath: string): Promise => { + await this.ensureContentReady() + if (!this.renderOnRequest) return false + let pathname: string + try { + pathname = decodeURIComponent(urlPath) + } catch { + return false + } + if (pathname.startsWith('/pagefind/')) { + await this.queueAssetWork(async () => { + if (this.searchRevision === this.pageAssetRevision) return + const revision = this.pageAssetRevision + await buildPagefindIndex( + this.config.outDir, + this.config.pagefind, + this.contentState.unindexedOutPaths(), + Object.values(this.manifest.content).map((entry) => entry.outPath), + ) + this.searchRevision = revision + }) + return true + } + const asset = + this.staticAssets.get(pathname) ?? + this.staticAssets.get( + pathname.endsWith('/') + ? `${pathname}index.html` + : `${pathname}/index.html`, + ) + if (!asset) return false + await this.queueAssetWork(async () => { + if (this.manifest.assets[asset.relPath]) return + const signature = await readSignature(asset.absPath) + if (!signature) return + const copied = await copyStaticAsset( + this.config.contentDir, + this.config.outDir, + asset, + ) + if (!copied.copied) return + this.manifest.assets[asset.relPath] = { + relPath: asset.relPath, + ext: asset.ext, + outPath: copied.outPath, + ...signature, + } + }) + return true + } + + private queueAssetWork(work: () => Promise): Promise { + const task = this.assetWork.then(work) + this.assetWork = task.catch(() => undefined) + return task + } + + private persistManifest(): Promise { + const task = this.manifestWork.then(() => this.writeManifestAndSitemap()) + this.manifestWork = task.catch(() => undefined) + return task } - private async persistManifest() { + private async writeManifestAndSitemap() { await writeManifest(this.config.outDir, this.manifest) const sitemap = await this.writeSitemapFromManifest() if (!sitemap) return diff --git a/packages/ts-ssg/src/build/incremental/script-entry-manager.ts b/packages/ts-ssg/src/build/incremental/script-entry-manager.ts index 41b1033d..2d6d3c7a 100644 --- a/packages/ts-ssg/src/build/incremental/script-entry-manager.ts +++ b/packages/ts-ssg/src/build/incremental/script-entry-manager.ts @@ -14,7 +14,7 @@ interface ScriptEntrypointManagerInput { minifyScripts: boolean failOnAssetError: boolean cacheBusting: boolean - assets: Record + getAssets: () => Record scriptCacheKeys: ScriptCacheKeyStore persistManifest: () => Promise } @@ -23,6 +23,8 @@ export class ScriptEntrypointManager { private readonly pageEntrypoints = new Map>() private readonly entryDependencies = new Map>() private readonly dependentsByRelPath = new Map>() + private readonly dirtyEntrypoints = new Map() + private dirtyVersion = 0 constructor(private readonly input: ScriptEntrypointManagerInput) {} @@ -54,6 +56,17 @@ export class ScriptEntrypointManager { this.removeEntrypoint(entryRelPath) } + invalidateEntrypoints(entries: Iterable) { + for (const entry of entries) { + this.input.scriptCacheKeys.bump(entry) + // Retain dependency tracking so subsequent edits still find importers. + this.dirtyEntrypoints.set( + this.normalizeRelPath(entry), + ++this.dirtyVersion, + ) + } + } + usesCacheBusting() { return this.input.cacheBusting } @@ -61,6 +74,11 @@ export class ScriptEntrypointManager { resolveImpactedEntryRelPaths(changedRelPath: string) { const normalized = this.normalizeRelPath(changedRelPath) const impacted = new Set() + // Failed bundles have no dependency graph yet. A TypeScript edit may fix + // their entrypoint or create a missing import, so allow them to retry. + for (const entry of this.collectDesiredEntrypoints()) { + if (!this.entryDependencies.has(entry)) impacted.add(entry) + } if (this.entryDependencies.has(normalized)) { impacted.add(normalized) } @@ -112,17 +130,17 @@ export class ScriptEntrypointManager { const nextEntries = this.collectDesiredEntrypoints() const currentEntries = new Set([ ...this.entryDependencies.keys(), - ...Object.values(this.input.assets) + ...Object.values(this.input.getAssets()) .filter((entry) => entry.ext.toLowerCase() === '.ts') .map((entry) => this.normalizeRelPath(entry.relPath)), ]) for (const entryRelPath of currentEntries) { if (nextEntries.has(entryRelPath)) continue - const priorEntry = this.input.assets[entryRelPath] + const priorEntry = this.input.getAssets()[entryRelPath] if (priorEntry) { await removeFile(priorEntry.outPath) - delete this.input.assets[entryRelPath] + delete this.input.getAssets()[entryRelPath] result.deletedAssets += 1 } this.input.scriptCacheKeys.remove(entryRelPath) @@ -141,11 +159,25 @@ export class ScriptEntrypointManager { return this.getEntrypointAssetFiles() } + /** Only requested pages register scripts in the dev server. */ + async buildMissingEntrypoints(result: IncrementalBuildResult) { + for (const entry of this.collectDesiredEntrypoints()) { + if ( + !this.entryDependencies.has(entry) || + this.dirtyEntrypoints.has(entry) + ) { + await this.rebuildSingleEntrypoint(entry, result) + } + } + } + private async rebuildSingleEntrypoint( entryRelPath: string, result: IncrementalBuildResult, options: { bumpCacheKeys?: boolean } = {}, ) { + const normalized = this.normalizeRelPath(entryRelPath) + const dirtyVersion = this.dirtyEntrypoints.get(normalized) const ext = path.extname(entryRelPath).toLowerCase() || '.ts' const assetFile = toAssetFile( this.input.config.contentDir, @@ -154,10 +186,10 @@ export class ScriptEntrypointManager { ) const signature = await readSignature(assetFile.absPath) if (!signature) { - const priorEntry = this.input.assets[entryRelPath] + const priorEntry = this.input.getAssets()[entryRelPath] if (priorEntry) { await removeFile(priorEntry.outPath) - delete this.input.assets[entryRelPath] + delete this.input.getAssets()[entryRelPath] result.deletedAssets += 1 } this.input.scriptCacheKeys.remove(entryRelPath) @@ -185,12 +217,12 @@ export class ScriptEntrypointManager { } return false } - const priorEntry = this.input.assets[entryRelPath] + const priorEntry = this.input.getAssets()[entryRelPath] if (priorEntry?.outPath && priorEntry.outPath !== assetCopy.outPath) { await removeFile(priorEntry.outPath) } - this.input.assets[entryRelPath] = { + this.input.getAssets()[entryRelPath] = { relPath: entryRelPath, ext, outPath: assetCopy.outPath, @@ -198,6 +230,9 @@ export class ScriptEntrypointManager { ...signature, } result.changedAssets += 1 + if (this.dirtyEntrypoints.get(normalized) === dirtyVersion) { + this.dirtyEntrypoints.delete(normalized) + } this.setEntrypointDependencies( entryRelPath, assetCopy.dependencyRelPaths.length > 0 diff --git a/packages/ts-ssg/src/build/incremental/support.ts b/packages/ts-ssg/src/build/incremental/support.ts index 58bdf5e9..4b52198f 100644 --- a/packages/ts-ssg/src/build/incremental/support.ts +++ b/packages/ts-ssg/src/build/incremental/support.ts @@ -52,26 +52,25 @@ export class ManifestContentIndex { ) {} rebuildFromManifest(manifest: BuildManifest) { + this.clear() + for (const entry of Object.values(manifest.content)) { + this.set(entry.relPath, entry.outPath, entry.ext) + } + } + + clear() { this.outPathToRelPath.clear() this.relPathToOutPath.clear() this.urlPathToRelPath.clear() this.relPathToUrlPath.clear() - for (const entry of Object.values(manifest.content)) { - this.set(entry.relPath, entry.outPath, entry.ext) - } } set(relPath: string, outPath: string, ext?: string) { - const prevOutPath = this.relPathToOutPath.get(relPath) - if (prevOutPath && prevOutPath !== outPath) { - this.outPathToRelPath.delete(prevOutPath) - } + this.setOutPath(relPath, outPath) const prevUrlPath = this.relPathToUrlPath.get(relPath) if (prevUrlPath) { this.urlPathToRelPath.delete(prevUrlPath) } - this.relPathToOutPath.set(relPath, outPath) - this.outPathToRelPath.set(outPath, relPath) const routeInfo = resolveRouteInfo(this.toContentFile(relPath, ext)) this.relPathToUrlPath.set(relPath, routeInfo.urlPath) if (!this.urlPathToRelPath.has(routeInfo.urlPath)) { @@ -79,6 +78,15 @@ export class ManifestContentIndex { } } + setOutPath(relPath: string, outPath: string) { + const prevOutPath = this.relPathToOutPath.get(relPath) + if (prevOutPath && prevOutPath !== outPath) { + this.outPathToRelPath.delete(prevOutPath) + } + this.relPathToOutPath.set(relPath, outPath) + this.outPathToRelPath.set(outPath, relPath) + } + remove(relPath: string) { const outPath = this.relPathToOutPath.get(relPath) const urlPath = this.relPathToUrlPath.get(relPath) diff --git a/packages/ts-ssg/src/build/incremental/types.ts b/packages/ts-ssg/src/build/incremental/types.ts index 3eddfcde..dc64d923 100644 --- a/packages/ts-ssg/src/build/incremental/types.ts +++ b/packages/ts-ssg/src/build/incremental/types.ts @@ -12,8 +12,14 @@ export interface IncrementalBuildResult { } export interface IncrementalBuilder { + /** Prepares shared state without rendering pages or copying static assets. */ + prepareForRequests: () => Promise buildAll: (reason: string) => Promise applyChange: (filePath: string) => Promise renderIfDirtyByOutPath: (outPath: string) => Promise - renderByUrlPath: (urlPath: string) => Promise + renderByUrlPath: (urlPath: string, locale?: string) => Promise + /** Writes styles and discovered scripts before a dev response is served. */ + preparePageAssets: () => Promise + /** Returns true when the URL belongs to a discovered asset or dev search. */ + prepareAssetByUrlPath: (urlPath: string) => Promise } diff --git a/packages/ts-ssg/src/build/pagefind.test.ts b/packages/ts-ssg/src/build/pagefind.test.ts index b54170bc..201b5773 100644 --- a/packages/ts-ssg/src/build/pagefind.test.ts +++ b/packages/ts-ssg/src/build/pagefind.test.ts @@ -59,4 +59,30 @@ describe('buildPagefindIndex', () => { await fs.rm(outDir, { recursive: true, force: true }) } }) + + it('indexes only the selected pages when other HTML remains in output', async () => { + const outDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ts-ssg-pagefind-')) + try { + const opened = path.join(outDir, 'index.html') + const stale = path.join(outDir, 'stale.html') + await fs.writeFile( + opened, + 'Opened page', + ) + await fs.writeFile( + stale, + 'Stale page', + ) + const result = await buildPagefindIndex( + outDir, + { enabled: true, excludePaths: [] }, + [], + [opened], + ) + expect(result.indexedPages).toBe(1) + expect(result.errors).toEqual([]) + } finally { + await fs.rm(outDir, { recursive: true, force: true }) + } + }) }) diff --git a/packages/ts-ssg/src/build/pagefind.ts b/packages/ts-ssg/src/build/pagefind.ts index cd44c138..27d6e58b 100644 --- a/packages/ts-ssg/src/build/pagefind.ts +++ b/packages/ts-ssg/src/build/pagefind.ts @@ -18,14 +18,16 @@ const PAGEFIND_DIRNAME = 'pagefind' * Indexes the pages in `outDir` for search. `config.excludePaths` skips * pages by URL prefix; `skipFiles` skips individual pages, such as those * whose frontmatter sets `index: false`. + * `onlyFiles` limits a dev index to pages rendered in the current session. */ export async function buildPagefindIndex( outDir: string, config: PagefindConfig, skipFiles: Iterable = [], + onlyFiles?: Iterable, ): Promise { const log = getLogger() - const state = createPagefindBuildState(outDir, config, skipFiles) + const state = createPagefindBuildState(outDir, config, skipFiles, onlyFiles) if (!config.enabled) { await removePagefindOutput(state.outputPath) log.info('pagefind indexing disabled', { @@ -78,12 +80,14 @@ type PagefindBuildState = { errors: string[] excludePaths: string[] skipFiles: ReadonlySet + onlyFiles: ReadonlySet | undefined } function createPagefindBuildState( outDir: string, config: PagefindConfig, skipFiles: Iterable, + onlyFiles?: Iterable, ): PagefindBuildState { return { startedAt: now(), @@ -97,6 +101,10 @@ function createPagefindBuildState( skipFiles: new Set( [...skipFiles].map((filePath) => path.resolve(filePath)), ), + onlyFiles: + onlyFiles === undefined + ? undefined + : new Set([...onlyFiles].map((filePath) => path.resolve(filePath))), } } @@ -149,15 +157,22 @@ async function populateAndWritePagefindIndex( ) { const { outDir, outputPath, excludePaths, skipFiles } = state const isSkipped = (filePath: string) => + (state.onlyFiles !== undefined && + !state.onlyFiles.has(path.resolve(filePath))) || skipFiles.has(path.resolve(filePath)) || shouldSkipPagefindPath( toPosixPath(path.relative(outDir, filePath)), excludePaths, ) - const htmlFiles = await collectHtmlFiles(outDir) + const htmlFiles = + state.onlyFiles === undefined + ? await collectHtmlFiles(outDir) + : [...state.onlyFiles] const indexedBytes = await measureHtmlFiles(htmlFiles, isSkipped) const indexed = - excludePaths.length === 0 && skipFiles.size === 0 + excludePaths.length === 0 && + skipFiles.size === 0 && + state.onlyFiles === undefined ? await indexHtmlDirectory(index, outDir) : await indexHtmlFiles(index, outDir, htmlFiles, isSkipped) const writeResult = await index.writeFiles({ outputPath }) diff --git a/packages/ts-ssg/src/cli-runner.ts b/packages/ts-ssg/src/cli-runner.ts index 30823bde..e7df4e94 100644 --- a/packages/ts-ssg/src/cli-runner.ts +++ b/packages/ts-ssg/src/cli-runner.ts @@ -118,6 +118,9 @@ function parseCliArgs(args: string[]): CliState { if (options.includes('--no-reload')) { state.input.liveReload = false } + if (options.includes('--full-render')) { + state.input.fullRender = true + } if (options.includes('--clean')) { state.input.build ??= {} state.input.build.options ??= {} @@ -167,7 +170,8 @@ function assertKnownOptions( function resolveFlagOptions(command: Exclude) { if (command === 'build') return ['--clean'] - if (command === 'serve') return ['--clean', '--no-watch', '--no-reload'] + if (command === 'serve') + return ['--clean', '--no-watch', '--no-reload', '--full-render'] return [] } @@ -203,10 +207,10 @@ class CliUsageError extends Error { const USAGE = `Usage: purestack build --content [--clean] - purestack serve --content [--host ] [--port ] [--clean] [--no-watch] [--no-reload] + purestack serve --content [--host ] [--port ] [--clean] [--no-watch] [--no-reload] [--full-render] purestack publish --content Commands: build Build a content directory into its configured outDir. - serve Start the dev server for a content directory. + serve Start the dev server; --full-render builds all pages once at startup. publish Clean and build a publish artifact using the configured publishDir.` diff --git a/packages/ts-ssg/src/config/head.ts b/packages/ts-ssg/src/config/head.ts index 1f983df0..8a6df37c 100644 --- a/packages/ts-ssg/src/config/head.ts +++ b/packages/ts-ssg/src/config/head.ts @@ -25,7 +25,7 @@ export function getHead(config?: BasicHeadConfig) { const head = createHead(getHeadConfig(merge(DEFAULTS, config))).push( h('meta').attr({ name: 'generator', - content: 'PureStack v1.1.3', + content: 'PureStack v1.1.4', }), h('meta').attr({ name: 'color-scheme', diff --git a/packages/ts-ssg/src/dev/server.test.ts b/packages/ts-ssg/src/dev/server.test.ts index ccd76920..15ff7772 100644 --- a/packages/ts-ssg/src/dev/server.test.ts +++ b/packages/ts-ssg/src/dev/server.test.ts @@ -30,6 +30,98 @@ describe('dev server', () => { root = undefined }) + it.each([true, false])( + 'renders only requested pages and copies requested assets (clean %s)', + async (cleanOutDir) => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-startup-') + const contentDir = path.join(root, 'content') + const outDir = path.join(root, 'out') + await writeFile( + path.join(contentDir, 'index.mdx'), + '# Home\n\n', + ) + await writeFile(path.join(contentDir, 'unused.mdx'), '# Unused page') + await writeFile( + path.join(contentDir, 'home.ts'), + "console.log('home ready')", + ) + await writeFile( + path.join(contentDir, 'assets', 'wanted.txt'), + 'wanted asset', + ) + await writeFile( + path.join(contentDir, 'assets', 'unused.txt'), + 'unused asset', + ) + await writeFile(path.join(outDir, 'index.html'), 'stale page') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + watch: false, + build: { + siteConfig: { + rootDir: root, + contentDir, + outDir, + mdx: { disableHighlighter: true }, + }, + options: { + cleanOutDir, + plugins: [ + definePlugin({ + name: 'render-count', + hooks: { + onPageStart(_context, file) { + renders.push(file.relPath) + }, + }, + }), + ], + }, + }, + }) + liveReload = listenForLiveReload(port) + await liveReload.reachVersion(1) + expect(renders).toEqual([]) + await expect( + fs.access(path.join(outDir, 'unused', 'index.html')), + ).rejects.toThrow() + await expect( + fs.access(path.join(outDir, 'assets', 'wanted.txt')), + ).rejects.toThrow() + const origin = `http://${HOST}:${port}` + const responses = await Promise.all([fetch(origin), fetch(origin)]) + for (const response of responses) { + expect(response.status).toBe(200) + const html = await response.text() + expect(html).toContain('Home') + expect(html).not.toContain('stale page') + const script = /]*\bsrc="([^"]+\.js)"/.exec(html)?.[1] + if (!script) throw new Error('Expected the home script bundle') + const bundle = await fetch(new URL(script, origin)) + expect(bundle.status).toBe(200) + expect(await bundle.text()).toContain('home ready') + const styles = [...html.matchAll(/]*href="([^"]+\.css)"/g)] + expect(styles.length).toBeGreaterThan(0) + for (const [, href] of styles) { + const css = await fetch(new URL(href, origin)) + expect(css.status).toBe(200) + expect((await css.text()).length).toBeGreaterThan(0) + } + } + expect(await get(port, '/')).toContain('Home') + expect(renders).toEqual(['index.mdx']) + expect(await get(port, '/assets/wanted.txt')).toBe('wanted asset') + await expect( + fs.access(path.join(outDir, 'assets', 'unused.txt')), + ).rejects.toThrow() + expect(await get(port, '/unused/')).toContain('Unused page') + expect(renders).toEqual(['index.mdx', 'unused.mdx']) + }, + 20_000, + ) it('reloads purestack.config.ts when a file it imports changes', async () => { root = await makeRepoTempDir('.tmp-ts-ssg-dev-config-') const contentDir = path.join(root, 'content') @@ -79,6 +171,312 @@ describe('dev server', () => { expect(await get(port, '/')).toContain('') }, 20_000) + it('fully builds once at startup and renders watched edits on request', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-full-') + const contentDir = path.join(root, 'content') + const outDir = path.join(root, 'out') + await writeFile(path.join(contentDir, 'index.mdx'), '# Home') + await writeFile(path.join(contentDir, 'other.mdx'), '# Other') + await writeFile(path.join(contentDir, 'asset.txt'), 'Copied asset') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + fullRender: true, + build: { + siteConfig: { + rootDir: root, + contentDir, + outDir, + mdx: { disableHighlighter: true }, + }, + options: { + cleanOutDir: true, + plugins: [ + definePlugin({ + name: 'count', + hooks: { + onPageStart(_context, file) { + renders.push(file.relPath) + }, + }, + }), + ], + }, + }, + }) + liveReload = listenForLiveReload(port) + await liveReload.reachVersion(1) + expect(renders).toEqual(['index.mdx', 'other.mdx']) + expect( + await fs.readFile(path.join(outDir, 'other', 'index.html'), 'utf8'), + ).toContain('Other') + expect(await fs.readFile(path.join(outDir, 'asset.txt'), 'utf8')).toBe( + 'Copied asset', + ) + await expect( + fs.access(path.join(outDir, 'pagefind', 'pagefind.js')), + ).resolves.toBeUndefined() + expect(await get(port, '/')).toContain('Home') + expect(renders).toHaveLength(2) + await writeFile(path.join(contentDir, 'header.mdx'), '

New header

') + await liveReload.reachVersion(2) + expect(renders).toEqual(['index.mdx', 'other.mdx']) + expect( + await fs.readFile(path.join(outDir, 'other', 'index.html'), 'utf8'), + ).not.toContain('New header') + expect(await get(port, '/')).toContain('New header') + expect(renders).toEqual(['index.mdx', 'other.mdx', 'index.mdx']) + expect(await get(port, '/other/')).toContain('New header') + expect(renders).toHaveLength(4) + await writeFile(path.join(contentDir, 'index.mdx'), '# Updated home') + await liveReload.reachVersion(3) + expect(renders).toHaveLength(4) + expect(await get(port, '/')).toContain('Updated home') + expect(renders).toHaveLength(5) + }, 20_000) + + it('invalidates edited pages and scripts without rendering until requested', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-edits-') + const contentDir = path.join(root, 'content') + const outDir = path.join(root, 'out') + const home = path.join(contentDir, 'index.mdx') + const unused = path.join(contentDir, 'unused.mdx') + const dependency = path.join(contentDir, '_dependency.ts') + const asset = path.join(contentDir, 'asset.txt') + const app = '' + await writeFile(home, `# Home\n\n${app}`) + await writeFile(unused, '# Unused') + await writeFile( + path.join(contentDir, 'home.ts'), + "import { marker } from './_dependency'; console.log(marker)", + ) + await writeFile(dependency, "export const marker = 'dependency v1'") + await writeFile(asset, 'asset v1') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + build: { + siteConfig: { + rootDir: root, + contentDir, + outDir, + mdx: { disableHighlighter: true }, + }, + options: { + plugins: [ + definePlugin({ + name: 'count', + hooks: { + onPageStart(_context, file) { + renders.push(file.relPath) + }, + }, + }), + ], + }, + }, + }) + liveReload = listenForLiveReload(port) + await liveReload.reachVersion(1) + expect(await get(port, '/')).toContain('Home') + expect(renders).toEqual(['index.mdx']) + + await writeFile(unused, '# Unused changed') + await liveReload.reachVersion(2) + expect(renders).toEqual(['index.mdx']) + await writeFile(home, `# Updated home\n\n${app}`) + await liveReload.reachVersion(3) + expect(renders).toEqual(['index.mdx']) + expect(await get(port, '/')).toContain('Updated home') + expect(renders).toEqual(['index.mdx', 'index.mdx']) + + await writeFile(dependency, "export const marker = 'dependency v2'") + await liveReload.reachVersion(4) + expect(renders).toHaveLength(2) + await writeFile(dependency, "export const marker = 'dependency v3'") + await liveReload.reachVersion(5) + expect(renders).toHaveLength(2) + const html = await get(port, '/') + const script = /]*\bsrc="([^"]+\.js)"/.exec(html)?.[1] + if (!script) throw new Error('Expected a script bundle') + expect(await get(port, script)).toContain('dependency v3') + expect(renders).toEqual(['index.mdx', 'index.mdx', 'index.mdx']) + + expect(await get(port, '/asset.txt')).toBe('asset v1') + await writeFile(asset, 'asset v2') + await liveReload.reachVersion(6) + await expect(fs.access(path.join(outDir, 'asset.txt'))).rejects.toThrow() + expect(await get(port, '/asset.txt')).toBe('asset v2') + await fs.rm(unused) + await liveReload.reachVersion(7) + expect((await fetch(`http://${HOST}:${port}/unused/`)).status).toBe(404) + expect(renders).toHaveLength(3) + }, 20_000) + + it('invalidates an edit made after a page renders during the full startup build', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-startup-edit-') + const contentDir = path.join(root, 'content') + const home = path.join(contentDir, 'index.mdx') + await writeFile(home, '# Original home') + await writeFile(path.join(contentDir, 'other.mdx'), '# Other') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + fullRender: true, + build: { + siteConfig: { + rootDir: root, + contentDir, + outDir: path.join(root, 'out'), + }, + options: { + plugins: [ + definePlugin({ + name: 'startup-edit', + hooks: { + async onPageStart(_context, file) { + renders.push(file.relPath) + if (file.relPath === 'other.mdx') + await writeFile(home, '# Changed during startup') + }, + }, + }), + ], + }, + }, + }) + liveReload = listenForLiveReload(port) + await liveReload.reachVersion(2) + expect(renders).toEqual(['index.mdx', 'other.mdx']) + expect(await get(port, '/')).toContain('Changed during startup') + expect(renders).toEqual(['index.mdx', 'other.mdx', 'index.mdx']) + }, 20_000) + + it('serves extensionless assets, static directories, encoded routes, and direct page outputs', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-paths-') + const contentDir = path.join(root, 'content') + const outDir = path.join(root, 'out') + await writeFile(path.join(contentDir, 'download'), 'extensionless asset') + await writeFile( + path.join(contentDir, 'static', 'index.html'), + 'Static directory', + ) + await writeFile(path.join(contentDir, 'café.mdx'), '# Encoded page') + await writeFile(path.join(contentDir, 'guide.mdx'), '# Guide') + await writeFile( + path.join(outDir, 'removed', 'index.html'), + 'stale deleted page', + ) + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + watch: false, + build: { + siteConfig: { rootDir: root, contentDir, outDir }, + }, + }) + expect(await get(port, '/download')).toBe('extensionless asset') + expect(await get(port, '/static/')).toContain('Static directory') + expect(await get(port, '/caf%C3%A9/')).toContain('Encoded page') + expect(await get(port, '/guide/index.html')).toContain('Guide') + expect( + (await fetch(`http://${HOST}:${port}/removed/index.html`)).status, + ).toBe(404) + }, 20_000) + + it('builds dev search from opened pages without rendering unopened pages', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-search-') + const contentDir = path.join(root, 'content') + const outDir = path.join(root, 'out') + await writeFile(path.join(contentDir, 'index.mdx'), '# Home') + await writeFile(path.join(contentDir, 'unused.mdx'), '# Unused') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + watch: false, + build: { + siteConfig: { rootDir: root, contentDir, outDir }, + options: { + plugins: [ + definePlugin({ + name: 'count', + hooks: { + onPageStart(_context, file) { + renders.push(file.relPath) + }, + }, + }), + ], + }, + }, + }) + liveReload = listenForLiveReload(port) + await liveReload.reachVersion(1) + await expect(fs.access(path.join(outDir, 'pagefind'))).rejects.toThrow() + expect(await get(port, '/')).toContain('Home') + expect( + (await fetch(`http://${HOST}:${port}/pagefind/pagefind.js`)).status, + ).toBe(200) + expect(renders).toEqual(['index.mdx']) + await expect( + fs.access(path.join(outDir, 'unused', 'index.html')), + ).rejects.toThrow() + }, 20_000) + + it('renders the requested hidden locale under a base path', async () => { + root = await makeRepoTempDir('.tmp-ts-ssg-dev-locale-') + const contentDir = path.join(root, 'content') + await writeFile(path.join(contentDir, 'en', 'index.mdx'), '# English home') + await writeFile(path.join(contentDir, 'de', 'index.mdx'), '# German home') + const renders: string[] = [] + const port = await findFreePort() + server = await startDevServer({ + host: HOST, + port, + watch: false, + build: { + siteConfig: { + rootDir: root, + contentDir, + outDir: path.join(root, 'out'), + basePath: '/docs', + i18n: { + enabled: true, + locales: ['en', 'de'], + defaultLocale: 'en', + urlStrategy: 'hidden', + queryParam: 'lang', + }, + }, + options: { + plugins: [ + definePlugin({ + name: 'count', + hooks: { + onPageStart(_context, file) { + renders.push(file.relPath.replaceAll('\\', '/')) + }, + }, + }), + ], + }, + }, + }) + expect(await get(port, '/docs/?lang=de')).toContain('German home') + expect(renders).toEqual(['de/index.mdx']) + expect(await get(port, '/docs/?lang=en')).toContain('English home') + expect(renders).toEqual(['de/index.mdx', 'en/index.mdx']) + }, 20_000) + it('starts with a plugin skin selected in the site config', async () => { root = await makeRepoTempDir('.tmp-ts-ssg-dev-') const contentDir = path.join(root, 'content') @@ -227,7 +625,7 @@ describe('dev server', () => { }, }) liveReload = listenForLiveReload(port) - // The initial build ends with the first reload signal. + // The first signal announces that shared request state is ready. await liveReload.reachVersion(1) expect(await get(port, '/guides/')).toContain('Header v1') diff --git a/packages/ts-ssg/src/dev/server.ts b/packages/ts-ssg/src/dev/server.ts index 57dcbbe4..f513cbca 100644 --- a/packages/ts-ssg/src/dev/server.ts +++ b/packages/ts-ssg/src/dev/server.ts @@ -37,6 +37,8 @@ export interface DevServerOptions { port?: number watch?: boolean liveReload?: boolean + /** Builds the complete site once at startup, then renders changes on request. */ + fullRender?: boolean } export interface DevServerInput extends DevServerOptions { @@ -63,6 +65,7 @@ type ResolvedDevServerOptions = { port: number watch: boolean liveReload: boolean + fullRender: boolean } type RebuildRequestState = { @@ -91,11 +94,13 @@ export async function startDevServer( let devMiddleware = composeDevMiddleware(buildInput.options?.plugins ?? []) const log = getLogger() - const { host, port, watch, liveReload } = resolveDevServerOptions(input) + const { host, port, watch, liveReload, fullRender } = + resolveDevServerOptions(input) + let fullBuild: Promise | undefined const clients: LiveReloadClients = new Map() let liveReloadVersion = 0 - let initialBuildDone = false + let initialSetupDone = false let shuttingDown = false let watcher: { close: () => void } | undefined const requestState = createRebuildRequestState() @@ -175,18 +180,20 @@ export async function startDevServer( if (options?.reloadConfig) { devMiddleware = composeDevMiddleware(buildInput.options?.plugins ?? []) } - await incremental.buildAll(reason) - if (!initialBuildDone) { + if (fullRender && !initialSetupDone) { + fullBuild = incremental.buildAll(reason) + await fullBuild + } + await incremental.prepareForRequests() + if (!initialSetupDone) { log.info('serving at', { url: `http://${displayHost}:${port}${withBasePath(config.basePath, '/')}`, }) - initialBuildDone = true + initialSetupDone = true } notifyReload(reason) } catch (error) { - logError(log, error, 'build failed') - } finally { - // ensure rebuild flow completes even when build throws + logError(log, error, 'dev setup failed') } } @@ -241,7 +248,10 @@ export async function startDevServer( basePath: config.basePath, i18n: config.i18n, liveReload, - // A full rebuild replaces the builder, so requests ask for the current one. + waitForFullBuild: async () => { + if (fullRender) await fullBuild + }, + // Config changes replace the builder, so requests ask for the current one. getIncremental: () => incremental, handlePluginRequest: (req, res) => devMiddleware(req, res), clients, @@ -264,6 +274,7 @@ export async function startDevServer( outDir: config.outDir, liveReload, watch, + fullRender, }) }) @@ -343,6 +354,7 @@ function resolveDevServerOptions( port: input.port ?? DEFAULT_PORT, watch: input.watch ?? true, liveReload: input.liveReload ?? true, + fullRender: input.fullRender ?? false, } } @@ -352,7 +364,7 @@ function createRebuildRequestState(): RebuildRequestState { inFlight: false, pending: false, timer: undefined, - reason: 'initial build', + reason: 'initial setup', changedPaths: new Set(), } } @@ -364,6 +376,7 @@ type DevServerRequestHandlerInput = { basePath: string i18n: I18nConfig liveReload: boolean + waitForFullBuild: () => Promise getIncremental: () => IncrementalBuilder /** Resolves true when a plugin's dev middleware responded. */ handlePluginRequest: ( @@ -383,6 +396,7 @@ function createDevServerRequestHandler(input: DevServerRequestHandlerInput) { basePath, i18n, liveReload, + waitForFullBuild, getIncremental, handlePluginRequest, clients, @@ -391,7 +405,6 @@ function createDevServerRequestHandler(input: DevServerRequestHandlerInput) { } = input return async (req: http.IncomingMessage, res: http.ServerResponse) => { - const incremental = getIncremental() if (!req.url) { res.writeHead(400) res.end() @@ -425,18 +438,27 @@ function createDevServerRequestHandler(input: DevServerRequestHandlerInput) { const localePreference = resolveRequestLocale(req, i18n) persistQueryLocalePreference(res, i18n, localePreference) - res.setTimeout(REQUEST_TIMEOUT_MS, () => { - res.destroy() - }) - - const fileResult = await resolveRequestFile({ - outDir, - pathname: internalPathname, - locale: localePreference.locale, - i18n, - incremental, - log, - }) + let fileResult: Awaited> + let incremental: IncrementalBuilder + try { + await waitForFullBuild() + incremental = getIncremental() + res.setTimeout(REQUEST_TIMEOUT_MS, () => { + res.destroy() + }) + fileResult = await resolveRequestFile({ + outDir, + pathname: internalPathname, + locale: localePreference.locale, + i18n, + incremental, + }) + } catch (error) { + logError(log, error, 'request preparation failed') + if (!res.headersSent) res.writeHead(500) + res.end('Internal server error') + return + } if (!fileResult) { res.writeHead(404) res.end('Not found') @@ -469,39 +491,30 @@ type ResolveRequestFileInput = { locale?: string i18n: I18nConfig incremental: IncrementalBuilder - log: Logger } async function resolveRequestFile(input: ResolveRequestFileInput) { - const { outDir, pathname, locale, i18n, incremental, log } = input - let fileResult = await resolveStaticFile(outDir, pathname) - if (!fileResult && i18n.enabled && i18n.urlStrategy === 'hidden' && locale) { - fileResult = await resolveStaticFile( - outDir, - withHiddenLocale(locale, pathname), - ) + const { outDir, pathname, locale, i18n, incremental } = input + let decodedPathname: string + try { + decodedPathname = decodeURIComponent(pathname) + } catch { + return null } - if (!fileResult && isLikelyHtmlPath(pathname)) { - try { - const rendered = await incremental.renderByUrlPath(pathname) - if (rendered) { - fileResult = await resolveStaticFile(outDir, pathname) - if ( - !fileResult && - i18n.enabled && - i18n.urlStrategy === 'hidden' && - locale - ) { - fileResult = await resolveStaticFile( - outDir, - withHiddenLocale(locale, pathname), - ) - } - } - } catch (error) { - logError(log, error, 'lazy route render failed') - } + const asset = await incremental.prepareAssetByUrlPath(pathname) + if ( + !asset && + (isLikelyHtmlPath(decodedPathname) || + decodedPathname.endsWith('/index.html')) + ) { + if (!(await incremental.renderByUrlPath(decodedPathname, locale))) + return null } + let fileResult = + i18n.enabled && i18n.urlStrategy === 'hidden' && locale + ? await resolveStaticFile(outDir, withHiddenLocale(locale, pathname)) + : null + fileResult ??= await resolveStaticFile(outDir, pathname) return fileResult } @@ -552,6 +565,7 @@ async function serveResolvedFile(input: ServeResolvedFileInput): Promise { // A page marked dirty renders before it is served, so the response // always reflects the latest change. await incremental.renderIfDirtyByOutPath(filePath) + await incremental.preparePageAssets() const html = await fsPromises.readFile(filePath, 'utf8') const injected = liveReload ? injectLiveReload(html, liveReloadPath, liveReloadVersion) diff --git a/packages/ts-ssg/src/dev/static-files.ts b/packages/ts-ssg/src/dev/static-files.ts index 8445a721..4acac8ad 100644 --- a/packages/ts-ssg/src/dev/static-files.ts +++ b/packages/ts-ssg/src/dev/static-files.ts @@ -38,6 +38,9 @@ export async function resolveStaticFile(outDir: string, pathname: string) { try { const stats = await fsPromises.stat(candidate) + if (stats.isFile()) { + return { filePath: candidate, ext: path.extname(candidate).toLowerCase() } + } if (stats.isDirectory()) { candidate = path.join(candidate, 'index.html') } diff --git a/packages/ts-style/package.json b/packages/ts-style/package.json index feb302a6..ae7b668d 100644 --- a/packages/ts-style/package.json +++ b/packages/ts-style/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-style", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-style.mjs", "types": "./dist/ts-style.d.mts", diff --git a/packages/ts-util-node/package.json b/packages/ts-util-node/package.json index 22d4fbe7..702cf7c9 100644 --- a/packages/ts-util-node/package.json +++ b/packages/ts-util-node/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-util-node", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-util-node.mjs", "types": "./dist/ts-util-node.d.mts", diff --git a/packages/ts-util/package.json b/packages/ts-util/package.json index aa9fe8d8..c67ed165 100644 --- a/packages/ts-util/package.json +++ b/packages/ts-util/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-util", - "version": "1.1.3", + "version": "1.1.4", "packageManager": "yarn@4.9.2", "module": "./dist/ts-util.mjs", "types": "./dist/ts-util.d.mts", diff --git a/vite.config.ts b/vite.config.ts index afd62d4c..88bea77f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,12 +2,10 @@ import tsconfigPaths from 'vite-tsconfig-paths' import { defineConfig } from 'vitest/config' export default defineConfig({ - plugins: [ - tsconfigPaths({ - skip: (dir) => dir.includes('docs'), - ignoreConfigErrors: true, - }), - ], + resolve: { + tsconfigPaths: true, + }, + plugins: [], test: { include: [ 'packages/*/src/**/*.{test,spec}.{js,cjs,mjs,ts,cts,mts,jsx,tsx}', diff --git a/yarn.lock b/yarn.lock index bf79b327..a81622c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,13 +236,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/aix-ppc64@npm:0.27.2" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/aix-ppc64@npm:0.28.0" @@ -257,13 +250,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/android-arm64@npm:0.27.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-arm64@npm:0.28.0" @@ -278,13 +264,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/android-arm@npm:0.27.2" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-arm@npm:0.28.0" @@ -299,13 +278,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/android-x64@npm:0.27.2" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-x64@npm:0.28.0" @@ -320,13 +292,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/darwin-arm64@npm:0.27.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/darwin-arm64@npm:0.28.0" @@ -341,13 +306,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/darwin-x64@npm:0.27.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/darwin-x64@npm:0.28.0" @@ -362,13 +320,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/freebsd-arm64@npm:0.27.2" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/freebsd-arm64@npm:0.28.0" @@ -383,13 +334,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/freebsd-x64@npm:0.27.2" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/freebsd-x64@npm:0.28.0" @@ -404,13 +348,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-arm64@npm:0.27.2" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-arm64@npm:0.28.0" @@ -425,13 +362,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-arm@npm:0.27.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-arm@npm:0.28.0" @@ -446,13 +376,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-ia32@npm:0.27.2" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-ia32@npm:0.28.0" @@ -467,13 +390,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-loong64@npm:0.27.2" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-loong64@npm:0.28.0" @@ -488,13 +404,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-mips64el@npm:0.27.2" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-mips64el@npm:0.28.0" @@ -509,13 +418,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-ppc64@npm:0.27.2" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-ppc64@npm:0.28.0" @@ -530,13 +432,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-riscv64@npm:0.27.2" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-riscv64@npm:0.28.0" @@ -551,13 +446,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-s390x@npm:0.27.2" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-s390x@npm:0.28.0" @@ -572,13 +460,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/linux-x64@npm:0.27.2" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-x64@npm:0.28.0" @@ -593,13 +474,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/netbsd-arm64@npm:0.27.2" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/netbsd-arm64@npm:0.28.0" @@ -614,13 +488,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/netbsd-x64@npm:0.27.2" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/netbsd-x64@npm:0.28.0" @@ -635,13 +502,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/openbsd-arm64@npm:0.27.2" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openbsd-arm64@npm:0.28.0" @@ -656,13 +516,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/openbsd-x64@npm:0.27.2" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openbsd-x64@npm:0.28.0" @@ -677,13 +530,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/openharmony-arm64@npm:0.27.2" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openharmony-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openharmony-arm64@npm:0.28.0" @@ -698,13 +544,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/sunos-x64@npm:0.27.2" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/sunos-x64@npm:0.28.0" @@ -719,13 +558,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/win32-arm64@npm:0.27.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-arm64@npm:0.28.0" @@ -740,13 +572,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/win32-ia32@npm:0.27.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-ia32@npm:0.28.0" @@ -761,13 +586,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.2": - version: 0.27.2 - resolution: "@esbuild/win32-x64@npm:0.27.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-x64@npm:0.28.0" @@ -839,30 +657,30 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 +"@jridgewell/sourcemap-codec@npm:^1.6.0": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.24": - version: 0.3.29 - resolution: "@jridgewell/trace-mapping@npm:0.3.29" +"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/fb547ba31658c4d74eb17e7389f4908bf7c44cef47acb4c5baa57289daf68e6fe53c639f41f751b3923aca67010501264f70e7b49978ad1f040294b22c37b333 + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.31 - resolution: "@jridgewell/trace-mapping@npm:0.3.31" +"@jridgewell/trace-mapping@npm:^0.3.24": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + checksum: 10c0/fb547ba31658c4d74eb17e7389f4908bf7c44cef47acb4c5baa57289daf68e6fe53c639f41f751b3923aca67010501264f70e7b49978ad1f040294b22c37b333 languageName: node linkType: hard @@ -1080,13 +898,6 @@ __metadata: languageName: node linkType: hard -"@oxc-project/types@npm:=0.151.0": - version: 0.151.0 - resolution: "@oxc-project/types@npm:0.151.0" - checksum: 10c0/7dac5399345abf368449735c31c218439e6c3a2acf7b8cbc715a88b45989390208f61bea0f3eb28bee5a034a509a7f4142ae60069e6611b07c70a30867c8ca84 - languageName: node - linkType: hard - "@oxc-project/types@npm:=0.152.0": version: 0.152.0 resolution: "@oxc-project/types@npm:0.152.0" @@ -1296,13 +1107,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-android-arm-eabi@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.11" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@rolldown/binding-android-arm-eabi@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.12" @@ -1310,13 +1114,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-android-arm64@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-android-arm64@npm:1.2.11" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@rolldown/binding-android-arm64@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-android-arm64@npm:1.2.12" @@ -1324,13 +1121,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-darwin-arm64@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-darwin-arm64@npm:1.2.11" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@rolldown/binding-darwin-arm64@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-darwin-arm64@npm:1.2.12" @@ -1338,13 +1128,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-darwin-x64@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-darwin-x64@npm:1.2.11" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@rolldown/binding-darwin-x64@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-darwin-x64@npm:1.2.12" @@ -1352,13 +1135,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-freebsd-x64@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-freebsd-x64@npm:1.2.11" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@rolldown/binding-freebsd-x64@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-freebsd-x64@npm:1.2.12" @@ -1366,13 +1142,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.11" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.12" @@ -1380,13 +1149,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-arm64-gnu@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.11" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@rolldown/binding-linux-arm64-gnu@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.12" @@ -1394,13 +1156,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-arm64-musl@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.11" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@rolldown/binding-linux-arm64-musl@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.12" @@ -1408,13 +1163,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-ppc64-gnu@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.11" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - "@rolldown/binding-linux-ppc64-gnu@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.12" @@ -1422,13 +1170,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-s390x-gnu@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.11" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - "@rolldown/binding-linux-s390x-gnu@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.12" @@ -1436,13 +1177,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-x64-gnu@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.11" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@rolldown/binding-linux-x64-gnu@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.12" @@ -1450,13 +1184,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-linux-x64-musl@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.11" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@rolldown/binding-linux-x64-musl@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.12" @@ -1464,13 +1191,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-openharmony-arm64@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.11" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@rolldown/binding-openharmony-arm64@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.12" @@ -1478,13 +1198,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-win32-arm64-msvc@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.11" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@rolldown/binding-win32-arm64-msvc@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.12" @@ -1492,13 +1205,6 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-win32-x64-msvc@npm:1.2.11": - version: 1.2.11 - resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.11" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@rolldown/binding-win32-x64-msvc@npm:1.2.12": version: 1.2.12 resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.12" @@ -1513,181 +1219,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.57.0" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@rollup/rollup-android-arm64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-android-arm64@npm:4.57.0" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-arm64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.57.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-x64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.57.0" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-arm64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.57.0" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-x64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-freebsd-x64@npm:4.57.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.57.0" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.57.0" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.57.0" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-musl@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.57.0" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-loong64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.57.0" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-loong64-musl@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.57.0" - conditions: os=linux & cpu=loong64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.57.0" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-musl@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.57.0" - conditions: os=linux & cpu=ppc64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.57.0" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-musl@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.57.0" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.57.0" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.57.0" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-musl@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.57.0" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-openbsd-x64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-openbsd-x64@npm:4.57.0" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-openharmony-arm64@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.57.0" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.57.0" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-ia32-msvc@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.57.0" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-gnu@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.57.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.57.0": - version: 4.57.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.57.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@secretlint/core@npm:^10.2.2": version: 10.2.2 resolution: "@secretlint/core@npm:10.2.2" @@ -1808,13 +1339,6 @@ __metadata: languageName: node linkType: hard -"@standard-schema/spec@npm:^1.0.0": - version: 1.1.0 - resolution: "@standard-schema/spec@npm:1.1.0" - checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 - languageName: node - linkType: hard - "@tabler/icons@npm:^3.48.0": version: 3.48.0 resolution: "@tabler/icons@npm:3.48.0" @@ -1847,7 +1371,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0": +"@types/estree@npm:^1.0.0": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 @@ -2063,87 +1587,34 @@ __metadata: "@ungap/structured-clone@npm:^1.0.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a - languageName: node - linkType: hard - -"@vitest/expect@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/expect@npm:4.0.18" - dependencies: - "@standard-schema/spec": "npm:^1.0.0" - "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - chai: "npm:^6.2.1" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc - languageName: node - linkType: hard - -"@vitest/mocker@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/mocker@npm:4.0.18" - dependencies: - "@vitest/spy": "npm:4.0.18" - estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.21" - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/pretty-format@npm:4.0.18" - dependencies: - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94 - languageName: node - linkType: hard - -"@vitest/runner@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/runner@npm:4.0.18" - dependencies: - "@vitest/utils": "npm:4.0.18" - pathe: "npm:^2.0.3" - checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01 - languageName: node - linkType: hard - -"@vitest/snapshot@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/snapshot@npm:4.0.18" - dependencies: - "@vitest/pretty-format": "npm:4.0.18" - magic-string: "npm:^0.30.21" - pathe: "npm:^2.0.3" - checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43 + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a languageName: node linkType: hard -"@vitest/spy@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/spy@npm:4.0.18" - checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e +"@vitest/mocker@npm:5.0.3": + version: 5.0.3 + resolution: "@vitest/mocker@npm:5.0.3" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.31" + "@vitest/spy": "npm:5.0.3" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^1.2.3" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/fb8cc5a03f647bfc742943e6bb287625792ec20ecde9c8664547e07ca368f92809211d78b5be8c87fc0cb8f0ff747f420f6fc8b87ac42a550e37bec4923390c5 languageName: node linkType: hard -"@vitest/utils@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/utils@npm:4.0.18" - dependencies: - "@vitest/pretty-format": "npm:4.0.18" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb +"@vitest/spy@npm:5.0.3": + version: 5.0.3 + resolution: "@vitest/spy@npm:5.0.3" + checksum: 10c0/4ab0efc522b370a553682b5dce2730e0d5b8959313d62485f580851cb25bdec3c14f8f624ba621cdec92d3af8767ac2bc7fbc9e360c1b38025f527cceac676c4 languageName: node linkType: hard @@ -2712,10 +2183,10 @@ __metadata: languageName: node linkType: hard -"chai@npm:^6.2.1": - version: 6.2.2 - resolution: "chai@npm:6.2.2" - checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 +"chai@npm:^6.2.2": + version: 6.3.0 + resolution: "chai@npm:6.3.0" + checksum: 10c0/fd1bf7fc41295ba1eba18a44d68e204b3cb11b79c9ad51fd071de3ee85323c20d94afc07a386f1f5ff44a5da4a116614d419fc44a2d459e4b5b574aef9af4431 languageName: node linkType: hard @@ -2840,7 +2311,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.3.4": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -3071,10 +2542,10 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.7.0": - version: 1.7.0 - resolution: "es-module-lexer@npm:1.7.0" - checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b +"es-module-lexer@npm:^2.3.2": + version: 2.3.2 + resolution: "es-module-lexer@npm:2.3.2" + checksum: 10c0/5e7389424c43478439f12f9a6aca1750f6f99afa384fc3de329f4a45f152ab156671055008adaafc74960877abf8cc338aeebf6bed8c21297b146fd6eb7a22f8 languageName: node linkType: hard @@ -3087,95 +2558,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.27.0": - version: 0.27.2 - resolution: "esbuild@npm:0.27.2" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.2" - "@esbuild/android-arm": "npm:0.27.2" - "@esbuild/android-arm64": "npm:0.27.2" - "@esbuild/android-x64": "npm:0.27.2" - "@esbuild/darwin-arm64": "npm:0.27.2" - "@esbuild/darwin-x64": "npm:0.27.2" - "@esbuild/freebsd-arm64": "npm:0.27.2" - "@esbuild/freebsd-x64": "npm:0.27.2" - "@esbuild/linux-arm": "npm:0.27.2" - "@esbuild/linux-arm64": "npm:0.27.2" - "@esbuild/linux-ia32": "npm:0.27.2" - "@esbuild/linux-loong64": "npm:0.27.2" - "@esbuild/linux-mips64el": "npm:0.27.2" - "@esbuild/linux-ppc64": "npm:0.27.2" - "@esbuild/linux-riscv64": "npm:0.27.2" - "@esbuild/linux-s390x": "npm:0.27.2" - "@esbuild/linux-x64": "npm:0.27.2" - "@esbuild/netbsd-arm64": "npm:0.27.2" - "@esbuild/netbsd-x64": "npm:0.27.2" - "@esbuild/openbsd-arm64": "npm:0.27.2" - "@esbuild/openbsd-x64": "npm:0.27.2" - "@esbuild/openharmony-arm64": "npm:0.27.2" - "@esbuild/sunos-x64": "npm:0.27.2" - "@esbuild/win32-arm64": "npm:0.27.2" - "@esbuild/win32-ia32": "npm:0.27.2" - "@esbuild/win32-x64": "npm:0.27.2" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd - languageName: node - linkType: hard - "esbuild@npm:^0.28.0": version: 0.28.0 resolution: "esbuild@npm:0.28.0" @@ -3380,10 +2762,10 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.2": - version: 1.3.0 - resolution: "expect-type@npm:1.3.0" - checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd +"expect-type@npm:^1.4.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 languageName: node linkType: hard @@ -3484,7 +2866,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -3494,7 +2876,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -3582,13 +2964,6 @@ __metadata: languageName: node linkType: hard -"globrex@npm:^0.1.2": - version: 0.1.2 - resolution: "globrex@npm:0.1.2" - checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 - languageName: node - linkType: hard - "gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" @@ -4406,12 +3781,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" +"magic-string@npm:^1.2.3": + version: 1.4.2 + resolution: "magic-string@npm:1.4.2" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + "@jridgewell/sourcemap-codec": "npm:^1.6.0" + checksum: 10c0/d3e222c202f99ec4861e7316f473f9b1cfe335ebdf38d4f67335f842f3fe248f01ee00fd3e1a8206bcbe9855e0c41515d7c210300d8fcf6eefbb982d79e423c6 languageName: node linkType: hard @@ -5091,15 +4466,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" - bin: - nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b - languageName: node - linkType: hard - "nanoid@npm:^3.3.18": version: 3.3.19 resolution: "nanoid@npm:3.3.19" @@ -5164,13 +4530,6 @@ __metadata: languageName: node linkType: hard -"obug@npm:^2.1.1": - version: 2.1.1 - resolution: "obug@npm:2.1.1" - checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 - languageName: node - linkType: hard - "obug@npm:^2.1.4": version: 2.2.1 resolution: "obug@npm:2.2.1" @@ -5323,13 +4682,6 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^2.0.3": - version: 2.0.3 - resolution: "pathe@npm:2.0.3" - checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 - languageName: node - linkType: hard - "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -5351,7 +4703,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": +"picomatch@npm:^4.0.2": version: 4.0.3 resolution: "picomatch@npm:4.0.3" checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 @@ -5383,17 +4735,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.6": - version: 8.5.6 - resolution: "postcss@npm:8.5.6" - dependencies: - nanoid: "npm:^3.3.11" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 - languageName: node - linkType: hard - "prettier@npm:^3.8.1": version: 3.8.1 resolution: "prettier@npm:3.8.1" @@ -5461,9 +4802,8 @@ __metadata: tsx: "npm:^4.23.15" typescript: "npm:7.0.2" typescript-parser: "npm:typescript@6.0.3" - vite: "npm:^8.3.1" - vite-tsconfig-paths: "npm:^6.1.1" - vitest: "npm:^4.0.18" + vite: "npm:^8.3.2" + vitest: "npm:^5.0.3" languageName: unknown linkType: soft @@ -5673,7 +5013,7 @@ __metadata: languageName: node linkType: hard -"rolldown@npm:~1.2.7": +"rolldown@npm:~1.2.11, rolldown@npm:~1.2.7": version: 1.2.12 resolution: "rolldown@npm:1.2.12" dependencies: @@ -5731,154 +5071,6 @@ __metadata: languageName: node linkType: hard -"rolldown@npm:~1.2.9": - version: 1.2.11 - resolution: "rolldown@npm:1.2.11" - dependencies: - "@oxc-project/types": "npm:=0.151.0" - "@rolldown/binding-android-arm-eabi": "npm:1.2.11" - "@rolldown/binding-android-arm64": "npm:1.2.11" - "@rolldown/binding-darwin-arm64": "npm:1.2.11" - "@rolldown/binding-darwin-x64": "npm:1.2.11" - "@rolldown/binding-freebsd-x64": "npm:1.2.11" - "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.11" - "@rolldown/binding-linux-arm64-gnu": "npm:1.2.11" - "@rolldown/binding-linux-arm64-musl": "npm:1.2.11" - "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.11" - "@rolldown/binding-linux-s390x-gnu": "npm:1.2.11" - "@rolldown/binding-linux-x64-gnu": "npm:1.2.11" - "@rolldown/binding-linux-x64-musl": "npm:1.2.11" - "@rolldown/binding-openharmony-arm64": "npm:1.2.11" - "@rolldown/binding-win32-arm64-msvc": "npm:1.2.11" - "@rolldown/binding-win32-x64-msvc": "npm:1.2.11" - "@rolldown/pluginutils": "npm:^1.0.0" - dependenciesMeta: - "@rolldown/binding-android-arm-eabi": - optional: true - "@rolldown/binding-android-arm64": - optional: true - "@rolldown/binding-darwin-arm64": - optional: true - "@rolldown/binding-darwin-x64": - optional: true - "@rolldown/binding-freebsd-x64": - optional: true - "@rolldown/binding-linux-arm-gnueabihf": - optional: true - "@rolldown/binding-linux-arm64-gnu": - optional: true - "@rolldown/binding-linux-arm64-musl": - optional: true - "@rolldown/binding-linux-ppc64-gnu": - optional: true - "@rolldown/binding-linux-s390x-gnu": - optional: true - "@rolldown/binding-linux-x64-gnu": - optional: true - "@rolldown/binding-linux-x64-musl": - optional: true - "@rolldown/binding-openharmony-arm64": - optional: true - "@rolldown/binding-win32-arm64-msvc": - optional: true - "@rolldown/binding-win32-x64-msvc": - optional: true - bin: - rolldown: ./bin/cli.mjs - checksum: 10c0/43dfd083fb625a254c039bddfe04f3dd1d7cf8b84291ead835e3702e8050b7fee5e702c8280d3000b2541c0e24aaa3110bac4251bc2ca50a85fe1c8bbb48d2f5 - languageName: node - linkType: hard - -"rollup@npm:^4.43.0": - version: 4.57.0 - resolution: "rollup@npm:4.57.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.57.0" - "@rollup/rollup-android-arm64": "npm:4.57.0" - "@rollup/rollup-darwin-arm64": "npm:4.57.0" - "@rollup/rollup-darwin-x64": "npm:4.57.0" - "@rollup/rollup-freebsd-arm64": "npm:4.57.0" - "@rollup/rollup-freebsd-x64": "npm:4.57.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.57.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.57.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.57.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.57.0" - "@rollup/rollup-linux-loong64-gnu": "npm:4.57.0" - "@rollup/rollup-linux-loong64-musl": "npm:4.57.0" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.57.0" - "@rollup/rollup-linux-ppc64-musl": "npm:4.57.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.57.0" - "@rollup/rollup-linux-riscv64-musl": "npm:4.57.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.57.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.57.0" - "@rollup/rollup-linux-x64-musl": "npm:4.57.0" - "@rollup/rollup-openbsd-x64": "npm:4.57.0" - "@rollup/rollup-openharmony-arm64": "npm:4.57.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.57.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.57.0" - "@rollup/rollup-win32-x64-gnu": "npm:4.57.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.57.0" - "@types/estree": "npm:1.0.8" - fsevents: "npm:~2.3.2" - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-freebsd-arm64": - optional: true - "@rollup/rollup-freebsd-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-loong64-gnu": - optional: true - "@rollup/rollup-linux-loong64-musl": - optional: true - "@rollup/rollup-linux-ppc64-gnu": - optional: true - "@rollup/rollup-linux-ppc64-musl": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-riscv64-musl": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-openbsd-x64": - optional: true - "@rollup/rollup-openharmony-arm64": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-gnu": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 10c0/ed23752db5c6933d3af190808e022a5e612cc4468826f0f407211998a6e501cc7aa053975674f147e872b37c70da4d5ac5b5b91ad55661655c32618a8827aed7 - languageName: node - linkType: hard - "run-applescript@npm:^7.0.0": version: 7.1.0 resolution: "run-applescript@npm:7.1.0" @@ -6044,13 +5236,6 @@ __metadata: languageName: node linkType: hard -"siginfo@npm:^2.0.0": - version: 2.0.0 - resolution: "siginfo@npm:2.0.0" - checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 - languageName: node - linkType: hard - "signal-exit@npm:^3.0.2": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -6157,17 +5342,10 @@ __metadata: languageName: node linkType: hard -"stackback@npm:0.0.2": - version: 0.0.2 - resolution: "stackback@npm:0.0.2" - checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 - languageName: node - linkType: hard - -"std-env@npm:^3.10.0": - version: 3.10.0 - resolution: "std-env@npm:3.10.0" - checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f +"std-env@npm:^4.2.0": + version: 4.3.0 + resolution: "std-env@npm:4.3.0" + checksum: 10c0/1e90dc69f32f853375c6de45dbe7219c8399e2a3ab3a972fdcd1cf3c20fe0e3c13bf58d57d6193b551d37a81c50dfb3f51e07774e9e48a7dc5a22dabcad7acbd languageName: node linkType: hard @@ -6274,21 +5452,14 @@ __metadata: languageName: node linkType: hard -"tinybench@npm:^2.9.0": - version: 2.9.0 - resolution: "tinybench@npm:2.9.0" - checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c - languageName: node - linkType: hard - -"tinyexec@npm:^1.0.2": - version: 1.0.2 - resolution: "tinyexec@npm:1.0.2" - checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee +"tinybench@npm:^6.1.4": + version: 6.2.0 + resolution: "tinybench@npm:6.2.0" + checksum: 10c0/ae821428f0412324611d449290f8497c886c94f04bad8e19aa9b3a5060b4802e738c49f9eb493e4fd17e4d5e792290565e5079be647df1995aea731c82363815 languageName: node linkType: hard -"tinyexec@npm:^1.3.1": +"tinyexec@npm:^1.3.0, tinyexec@npm:^1.3.1": version: 1.3.1 resolution: "tinyexec@npm:1.3.1" checksum: 10c0/c590369cb3fa73cc18a3998caec8a00f9712d91309c881be3ac52b160fb2f80c7b1fbf3591858ab6abab47ad55385b0c86ec19982e32441a01f55298dab9ccd2 @@ -6305,16 +5476,6 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.15": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 - languageName: node - linkType: hard - "tinyglobby@npm:^0.2.17": version: 0.2.17 resolution: "tinyglobby@npm:0.2.17" @@ -6325,13 +5486,6 @@ __metadata: languageName: node linkType: hard -"tinyrainbow@npm:^3.0.3": - version: 3.0.3 - resolution: "tinyrainbow@npm:3.0.3" - checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c - languageName: node - linkType: hard - "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -6364,20 +5518,6 @@ __metadata: languageName: node linkType: hard -"tsconfck@npm:^3.0.3": - version: 3.1.6 - resolution: "tsconfck@npm:3.1.6" - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - bin: - tsconfck: bin/tsconfck.js - checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 - languageName: node - linkType: hard - "tsdown@npm:0.23.0": version: 0.23.0 resolution: "tsdown@npm:0.23.0" @@ -6807,83 +5947,15 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^6.1.1": - version: 6.1.1 - resolution: "vite-tsconfig-paths@npm:6.1.1" - dependencies: - debug: "npm:^4.1.1" - globrex: "npm:^0.1.2" - tsconfck: "npm:^3.0.3" - peerDependencies: - vite: "*" - checksum: 10c0/5e61080991418fefa08c5b98995cdcada4931ae01ac97ef9e2ee941051f61b76890a6e7ba48bed3b2a229ec06fef33a06621bba4ce457b3f4233ad31dc0c1d1b - languageName: node - linkType: hard - -"vite@npm:^6.0.0 || ^7.0.0": - version: 7.3.1 - resolution: "vite@npm:7.3.1" - dependencies: - esbuild: "npm:^0.27.0" - fdir: "npm:^6.5.0" - fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" - tinyglobby: "npm:^0.2.15" - peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 - jiti: ">=1.21.0" - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 - languageName: node - linkType: hard - -"vite@npm:^8.3.1": - version: 8.3.1 - resolution: "vite@npm:8.3.1" +"vite@npm:^8.3.2": + version: 8.3.2 + resolution: "vite@npm:8.3.2" dependencies: fsevents: "npm:~2.3.3" lightningcss: "npm:^1.33.0" picomatch: "npm:^4.0.7" postcss: "npm:^8.5.28" - rolldown: "npm:~1.2.9" + rolldown: "npm:~1.2.11" tinyglobby: "npm:^0.2.17" peerDependencies: "@types/node": ^20.19.0 || >=22.12.0 @@ -6928,44 +6000,40 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/a91f4fee1cd331912ee3ac45f49900044846e28b8632ae734fe1978630e643e503d04181b2aeac59f70556fc51137dd49179fd39cbeb72a3e5f0bce88d2bcd30 - languageName: node - linkType: hard - -"vitest@npm:^4.0.18": - version: 4.0.18 - resolution: "vitest@npm:4.0.18" - dependencies: - "@vitest/expect": "npm:4.0.18" - "@vitest/mocker": "npm:4.0.18" - "@vitest/pretty-format": "npm:4.0.18" - "@vitest/runner": "npm:4.0.18" - "@vitest/snapshot": "npm:4.0.18" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - es-module-lexer: "npm:^1.7.0" - expect-type: "npm:^1.2.2" - magic-string: "npm:^0.30.21" - obug: "npm:^2.1.1" - pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.3" - std-env: "npm:^3.10.0" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^1.0.2" - tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.0.3" - vite: "npm:^6.0.0 || ^7.0.0" - why-is-node-running: "npm:^2.3.0" + checksum: 10c0/bc2dc5b9077624686a0e4ddfdbfca9d35bf97a32c53966bf1cdd7f355a8f2c488c0422d1b02328a959cfd06b490f98c7e24938f8a251a1039adf2219f2da552e + languageName: node + linkType: hard + +"vitest@npm:^5.0.3": + version: 5.0.3 + resolution: "vitest@npm:5.0.3" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/mocker": "npm:5.0.3" + chai: "npm:^6.2.2" + es-module-lexer: "npm:^2.3.2" + expect-type: "npm:^1.4.0" + magic-string: "npm:^1.2.3" + obug: "npm:^2.1.4" + picomatch: "npm:^4.0.7" + std-env: "npm:^4.2.0" + tinybench: "npm:^6.1.4" + tinyexec: "npm:^1.3.0" + tinyglobby: "npm:^0.2.17" + why-is-node-running: "npm:3.2.1" peerDependencies: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 - "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.18 - "@vitest/browser-preview": 4.0.18 - "@vitest/browser-webdriverio": 4.0.18 - "@vitest/ui": 4.0.18 + "@types/node": ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 5.0.3 + "@vitest/browser-preview": 5.0.3 + "@vitest/browser-webdriverio": ^5.0.0-beta.5 || >=5.0.0 + "@vitest/coverage-istanbul": 5.0.3 + "@vitest/coverage-v8": 5.0.3 + "@vitest/ui": 5.0.3 happy-dom: "*" jsdom: "*" + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: "@edge-runtime/vm": optional: true @@ -6979,15 +6047,21 @@ __metadata: optional: true "@vitest/browser-webdriverio": optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true "@vitest/ui": optional: true happy-dom: optional: true jsdom: optional: true + vite: + optional: false bin: - vitest: vitest.mjs - checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa + vitest: ./vitest.mjs + checksum: 10c0/eaafd4f1fac2c52f4609d8af8de20668a2140883cd19f79d991fd0a85f2774d099ba6973c0ef65b6a5aa320f6fd62ff7f9a60b1e9d5bc7e4b89dc914e65003d2 languageName: node linkType: hard @@ -7020,15 +6094,12 @@ __metadata: languageName: node linkType: hard -"why-is-node-running@npm:^2.3.0": - version: 2.3.0 - resolution: "why-is-node-running@npm:2.3.0" - dependencies: - siginfo: "npm:^2.0.0" - stackback: "npm:0.0.2" +"why-is-node-running@npm:3.2.1": + version: 3.2.1 + resolution: "why-is-node-running@npm:3.2.1" bin: why-is-node-running: cli.js - checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + checksum: 10c0/e3f82673aac2c0a330fdbcf4b282ecdf7e9c773fa82fb4217db5b8b3d374cbb51e708d928f5b17999bc65bc1a5a90fad0f6bd794641bd15fdd525949ccee5e39 languageName: node linkType: hard