@lovrozagar/flare 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +146 -0
- package/scripts/download-fonts.ts +136 -0
- package/scripts/populate-fonts.ts +721 -0
- package/src/await.ts +2 -0
- package/src/boundaries/index.ts +99 -0
- package/src/broadcast/channel.ts +65 -0
- package/src/broadcast/context.ts +12 -0
- package/src/broadcast/index.ts +75 -0
- package/src/broadcast/provider.tsx +7 -0
- package/src/build-location.ts +2 -0
- package/src/caches/index.ts +384 -0
- package/src/client/index.ts +75 -0
- package/src/client.tsx +2 -0
- package/src/codegen/index.ts +41 -0
- package/src/components/await.tsx +140 -0
- package/src/components/dev-error-overlay.tsx +154 -0
- package/src/components/direction-script.tsx +22 -0
- package/src/components/index.ts +150 -0
- package/src/components/locale-script.tsx +26 -0
- package/src/components/reset-css.tsx +14 -0
- package/src/components/ssr-context.tsx +37 -0
- package/src/components/theme-script.tsx +22 -0
- package/src/components/view-transition-css.tsx +21 -0
- package/src/config/index.ts +84 -0
- package/src/create-client-only-fn.ts +1 -0
- package/src/create-isomorphic-fn.ts +2 -0
- package/src/create-layout.ts +1 -0
- package/src/create-page.ts +1 -0
- package/src/create-path-segment.ts +1 -0
- package/src/create-query-client-getter.ts +2 -0
- package/src/create-root-layout.ts +1 -0
- package/src/create-router.ts +8 -0
- package/src/create-server-only-fn.ts +1 -0
- package/src/dedupe/index.ts +151 -0
- package/src/defer/index.ts +82 -0
- package/src/direction/index.tsx +163 -0
- package/src/direction.ts +3 -0
- package/src/duration/index.ts +18 -0
- package/src/env-fn/index.ts +101 -0
- package/src/env-fn.ts +2 -0
- package/src/errors/index.ts +166 -0
- package/src/fonts/abeezee.ts +50 -0
- package/src/fonts/abel.ts +26 -0
- package/src/fonts/abril-fatface.ts +34 -0
- package/src/fonts/acme.ts +26 -0
- package/src/fonts/advent-pro.ts +92 -0
- package/src/fonts/alegreya-sans.ts +228 -0
- package/src/fonts/alegreya.ts +228 -0
- package/src/fonts/alex-brush.ts +42 -0
- package/src/fonts/alfa-slab-one.ts +42 -0
- package/src/fonts/alice.ts +48 -0
- package/src/fonts/amatic-sc.ts +94 -0
- package/src/fonts/amiri.ts +114 -0
- package/src/fonts/antic-slab.ts +26 -0
- package/src/fonts/anton.ts +42 -0
- package/src/fonts/archivo-black.ts +34 -0
- package/src/fonts/archivo-narrow.ts +114 -0
- package/src/fonts/archivo.ts +66 -0
- package/src/fonts/arimo.ts +256 -0
- package/src/fonts/arvo.ts +50 -0
- package/src/fonts/asap.ts +66 -0
- package/src/fonts/assistant.ts +64 -0
- package/src/fonts/baloo-2.ts +82 -0
- package/src/fonts/barlow-condensed.ts +114 -0
- package/src/fonts/barlow-semi-condensed.ts +114 -0
- package/src/fonts/barlow.ts +114 -0
- package/src/fonts/be-vietnam-pro.ts +114 -0
- package/src/fonts/bebas-neue.ts +34 -0
- package/src/fonts/besley.ts +82 -0
- package/src/fonts/biorhyme.ts +50 -0
- package/src/fonts/bitter.ts +94 -0
- package/src/fonts/black-ops-one.ts +49 -0
- package/src/fonts/bree-serif.ts +34 -0
- package/src/fonts/bricolage-grotesque.ts +66 -0
- package/src/fonts/cabin.ts +114 -0
- package/src/fonts/cairo.ts +66 -0
- package/src/fonts/cardo.ts +193 -0
- package/src/fonts/catamaran.ts +41 -0
- package/src/fonts/caveat.ts +78 -0
- package/src/fonts/chakra-petch.ts +142 -0
- package/src/fonts/chivo.ts +66 -0
- package/src/fonts/cinzel.ts +50 -0
- package/src/fonts/comfortaa.ts +109 -0
- package/src/fonts/commissioner.ts +64 -0
- package/src/fonts/component.tsx +27 -0
- package/src/fonts/cormorant-garamond.ts +171 -0
- package/src/fonts/cormorant.ts +170 -0
- package/src/fonts/courgette.ts +34 -0
- package/src/fonts/courier-prime.ts +82 -0
- package/src/fonts/create-font.ts +47 -0
- package/src/fonts/create-registry-font.ts +40 -0
- package/src/fonts/crete-round.ts +50 -0
- package/src/fonts/crimson-text.ts +114 -0
- package/src/fonts/css.ts +86 -0
- package/src/fonts/dancing-script.ts +66 -0
- package/src/fonts/dm-mono.ts +50 -0
- package/src/fonts/dm-sans.ts +50 -0
- package/src/fonts/dm-serif-display.ts +50 -0
- package/src/fonts/domine.ts +50 -0
- package/src/fonts/dosis.ts +66 -0
- package/src/fonts/eb-garamond.ts +228 -0
- package/src/fonts/eczar.ts +94 -0
- package/src/fonts/encode-sans.ts +42 -0
- package/src/fonts/epilogue.ts +66 -0
- package/src/fonts/exo-2.ts +94 -0
- package/src/fonts/exo.ts +66 -0
- package/src/fonts/figtree.ts +82 -0
- package/src/fonts/fira-code.ts +121 -0
- package/src/fonts/fira-sans-condensed.ts +228 -0
- package/src/fonts/fira-sans.ts +228 -0
- package/src/fonts/fjalla-one.ts +49 -0
- package/src/fonts/font-urls.gen.json +1962 -0
- package/src/fonts/fraunces.ts +66 -0
- package/src/fonts/fredoka.ts +64 -0
- package/src/fonts/gelasio.ts +114 -0
- package/src/fonts/gloria-hallelujah.ts +34 -0
- package/src/fonts/gothic-a1.ts +124 -0
- package/src/fonts/great-vibes.ts +64 -0
- package/src/fonts/gudea.ts +66 -0
- package/src/fonts/heebo.ts +57 -0
- package/src/fonts/hind-siliguri.ts +66 -0
- package/src/fonts/hind.ts +66 -0
- package/src/fonts/ibm-plex-mono.ts +172 -0
- package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
- package/src/fonts/ibm-plex-sans.ts +199 -0
- package/src/fonts/ibm-plex-serif.ts +171 -0
- package/src/fonts/inconsolata.ts +66 -0
- package/src/fonts/index.ts +4 -0
- package/src/fonts/indie-flower.ts +34 -0
- package/src/fonts/instrument-sans.ts +82 -0
- package/src/fonts/instrument-serif.ts +50 -0
- package/src/fonts/inter-display.ts +52 -0
- package/src/fonts/inter-tight.ts +124 -0
- package/src/fonts/inter.ts +123 -0
- package/src/fonts/jetbrains-mono.ts +199 -0
- package/src/fonts/josefin-sans.ts +114 -0
- package/src/fonts/josefin-slab.ts +50 -0
- package/src/fonts/jost.ts +64 -0
- package/src/fonts/kalam.ts +66 -0
- package/src/fonts/kanit.ts +142 -0
- package/src/fonts/karla.ts +82 -0
- package/src/fonts/khand.ts +66 -0
- package/src/fonts/lato.ts +82 -0
- package/src/fonts/league-spartan.ts +42 -0
- package/src/fonts/lexend-deca.ts +42 -0
- package/src/fonts/lexend.ts +42 -0
- package/src/fonts/libre-baskerville.ts +82 -0
- package/src/fonts/libre-franklin.ts +95 -0
- package/src/fonts/lilita-one.ts +34 -0
- package/src/fonts/lobster-two.ts +50 -0
- package/src/fonts/lobster.ts +56 -0
- package/src/fonts/lora.ts +235 -0
- package/src/fonts/m-plus-rounded-1c.ts +138 -0
- package/src/fonts/manrope.ts +109 -0
- package/src/fonts/maven-pro.ts +66 -0
- package/src/fonts/merriweather-sans.ts +142 -0
- package/src/fonts/merriweather.ts +171 -0
- package/src/fonts/montserrat-alternates.ts +171 -0
- package/src/fonts/montserrat.ts +94 -0
- package/src/fonts/mukta.ts +66 -0
- package/src/fonts/mulish.ts +170 -0
- package/src/fonts/nanum-gothic.ts +34 -0
- package/src/fonts/nanum-myeongjo.ts +34 -0
- package/src/fonts/neuton.ts +66 -0
- package/src/fonts/newsreader.ts +114 -0
- package/src/fonts/noticia-text.ts +114 -0
- package/src/fonts/noto-sans-arabic.ts +58 -0
- package/src/fonts/noto-sans-hk.ts +49 -0
- package/src/fonts/noto-sans-jp.ts +49 -0
- package/src/fonts/noto-sans-kr.ts +49 -0
- package/src/fonts/noto-sans-sc.ts +49 -0
- package/src/fonts/noto-sans-tc.ts +49 -0
- package/src/fonts/noto-sans.ts +140 -0
- package/src/fonts/noto-serif-jp.ts +80 -0
- package/src/fonts/noto-serif.ts +140 -0
- package/src/fonts/nunito-sans.ts +170 -0
- package/src/fonts/nunito.ts +170 -0
- package/src/fonts/old-standard-tt.ts +133 -0
- package/src/fonts/onest.ts +48 -0
- package/src/fonts/open-sans.ts +340 -0
- package/src/fonts/orbitron.ts +34 -0
- package/src/fonts/oswald.ts +94 -0
- package/src/fonts/outfit.ts +34 -0
- package/src/fonts/overpass.ts +94 -0
- package/src/fonts/oxygen.ts +50 -0
- package/src/fonts/pacifico.ts +56 -0
- package/src/fonts/passion-one.ts +50 -0
- package/src/fonts/pathway-gothic-one.ts +34 -0
- package/src/fonts/permanent-marker.ts +26 -0
- package/src/fonts/philosopher.ts +172 -0
- package/src/fonts/play.ts +109 -0
- package/src/fonts/playfair-display.ts +142 -0
- package/src/fonts/plus-jakarta-sans.ts +142 -0
- package/src/fonts/poppins.ts +114 -0
- package/src/fonts/prompt.ts +142 -0
- package/src/fonts/pt-sans-narrow.ts +78 -0
- package/src/fonts/pt-sans.ts +138 -0
- package/src/fonts/pt-serif.ts +138 -0
- package/src/fonts/public-sans.ts +66 -0
- package/src/fonts/questrial.ts +42 -0
- package/src/fonts/quicksand.ts +66 -0
- package/src/fonts/rajdhani.ts +66 -0
- package/src/fonts/raleway.ts +94 -0
- package/src/fonts/recursive.ts +80 -0
- package/src/fonts/red-hat-display.ts +82 -0
- package/src/fonts/red-rose.ts +66 -0
- package/src/fonts/registry.gen.ts +173 -0
- package/src/fonts/righteous.ts +34 -0
- package/src/fonts/roboto-condensed.ts +124 -0
- package/src/fonts/roboto-flex.ts +64 -0
- package/src/fonts/roboto-mono.ts +199 -0
- package/src/fonts/roboto-slab.ts +72 -0
- package/src/fonts/roboto.ts +156 -0
- package/src/fonts/rokkitt.ts +66 -0
- package/src/fonts/rubik.ts +199 -0
- package/src/fonts/sacramento.ts +34 -0
- package/src/fonts/saira.ts +66 -0
- package/src/fonts/sarabun.ts +142 -0
- package/src/fonts/satisfy.ts +26 -0
- package/src/fonts/sawarabi-mincho.ts +41 -0
- package/src/fonts/shadows-into-light.ts +34 -0
- package/src/fonts/signika-negative.ts +66 -0
- package/src/fonts/signika.ts +66 -0
- package/src/fonts/slabo-27px.ts +34 -0
- package/src/fonts/sora.ts +50 -0
- package/src/fonts/source-code-pro.ts +228 -0
- package/src/fonts/source-sans-3.ts +228 -0
- package/src/fonts/source-serif-4.ts +199 -0
- package/src/fonts/space-grotesk.ts +66 -0
- package/src/fonts/space-mono.ts +114 -0
- package/src/fonts/spectral.ts +170 -0
- package/src/fonts/tajawal.ts +50 -0
- package/src/fonts/teko.ts +66 -0
- package/src/fonts/tenor-sans.ts +41 -0
- package/src/fonts/tinos.ts +256 -0
- package/src/fonts/titillium-web.ts +82 -0
- package/src/fonts/types.ts +43 -0
- package/src/fonts/ubuntu-condensed.ts +63 -0
- package/src/fonts/ubuntu-mono.ts +195 -0
- package/src/fonts/ubuntu.ts +195 -0
- package/src/fonts/unbounded.ts +94 -0
- package/src/fonts/unicode-ranges.gen.ts +28 -0
- package/src/fonts/urbanist.ts +50 -0
- package/src/fonts/varela-round.ts +49 -0
- package/src/fonts/vollkorn.ts +199 -0
- package/src/fonts/work-sans.ts +66 -0
- package/src/fonts/yanone-kaffeesatz.ts +128 -0
- package/src/fonts/yantramanav.ts +66 -0
- package/src/fonts/yellowtail.ts +34 -0
- package/src/fonts/young-serif.ts +34 -0
- package/src/fonts/zen-kaku-gothic-new.ts +64 -0
- package/src/fonts/zilla-slab.ts +82 -0
- package/src/form/index.tsx +205 -0
- package/src/generators/index.ts +1589 -0
- package/src/head-client/index.ts +649 -0
- package/src/history/index.ts +210 -0
- package/src/hooks.ts +61 -0
- package/src/hydrate/index.tsx +381 -0
- package/src/hydration/index.ts +187 -0
- package/src/i18n/index.ts +250 -0
- package/src/i18n/jsx.tsx +42 -0
- package/src/i18n/message-format.ts +174 -0
- package/src/image/index.tsx +473 -0
- package/src/image.ts +12 -0
- package/src/intercept-outlet/index.tsx +18 -0
- package/src/intercept-outlet.ts +2 -0
- package/src/internal/index.ts +143 -0
- package/src/internal/once-idle.ts +24 -0
- package/src/internal.ts +12 -0
- package/src/lazy/index.tsx +205 -0
- package/src/link/index.tsx +367 -0
- package/src/loader-pipeline/index.ts +639 -0
- package/src/locale/index.tsx +137 -0
- package/src/locale.ts +3 -0
- package/src/logger.ts +22 -0
- package/src/middleware/builtins/api-proxy.ts +64 -0
- package/src/middleware/builtins/cdn-proxy.ts +58 -0
- package/src/middleware/builtins/i18n.ts +256 -0
- package/src/middleware/builtins/keepalive.ts +24 -0
- package/src/middleware/builtins/markdown-negotiation.ts +141 -0
- package/src/middleware/builtins/static-assets.ts +32 -0
- package/src/middleware/index.ts +223 -0
- package/src/module-graph/index.ts +181 -0
- package/src/mount/index.ts +146 -0
- package/src/navigate.ts +1 -0
- package/src/navigation/index.ts +1278 -0
- package/src/navigation/types.ts +59 -0
- package/src/navigation-progress/index.tsx +247 -0
- package/src/navigation-progress.ts +1 -0
- package/src/ndjson-client/index.ts +376 -0
- package/src/ndjson-server/index.ts +284 -0
- package/src/outlet/index.tsx +633 -0
- package/src/outlet/types.ts +243 -0
- package/src/plugins/css-transform.ts +70 -0
- package/src/plugins/dev-cdn-cache.ts +454 -0
- package/src/plugins/dev-dashboard/client.tsx +2603 -0
- package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
- package/src/plugins/dev-dashboard/plugin.ts +404 -0
- package/src/plugins/dev-prerender.ts +168 -0
- package/src/plugins/dev-server.ts +327 -0
- package/src/plugins/generate-watch.ts +32 -0
- package/src/plugins/image-plugin.ts +171 -0
- package/src/plugins/index.ts +445 -0
- package/src/plugins/options.ts +37 -0
- package/src/plugins/parse-utils.ts +59 -0
- package/src/plugins/prerender-plugin.ts +144 -0
- package/src/plugins/purge.ts +242 -0
- package/src/plugins/server-fn.ts +641 -0
- package/src/plugins/service-worker.ts +225 -0
- package/src/plugins/solid-jsx.d.ts +12 -0
- package/src/plugins/sx-ast/atomic.ts +166 -0
- package/src/plugins/sx-ast/evaluator.ts +235 -0
- package/src/plugins/sx-ast/index.ts +372 -0
- package/src/plugins/sx-ast/parser.ts +22 -0
- package/src/plugins/sx-ast/rewrite.ts +763 -0
- package/src/plugins/sx.d.ts +14 -0
- package/src/plugins/tw-compile.ts +240 -0
- package/src/plugins/types.ts +41 -0
- package/src/plugins/virtual.d.ts +110 -0
- package/src/plugins/virtual.ts +184 -0
- package/src/preload/index.ts +77 -0
- package/src/prerender/index.ts +431 -0
- package/src/query-client/index.tsx +168 -0
- package/src/registry/index.ts +88 -0
- package/src/reset-css.ts +1 -0
- package/src/revalidation/index.ts +84 -0
- package/src/rewrite/index.ts +83 -0
- package/src/route-builder/create-layout.ts +655 -0
- package/src/route-builder/create-page.ts +806 -0
- package/src/route-builder/create-path-segment.ts +34 -0
- package/src/route-builder/create-root-layout.ts +608 -0
- package/src/route-builder/index.ts +49 -0
- package/src/route-builder/register.ts +242 -0
- package/src/route-builder/types.ts +294 -0
- package/src/router-config/index.ts +91 -0
- package/src/router-primitives/index.ts +25 -0
- package/src/router-primitives/location.ts +29 -0
- package/src/router-primitives/match-id.ts +111 -0
- package/src/router-primitives/paths.ts +109 -0
- package/src/router-primitives/tree.ts +243 -0
- package/src/router-primitives/types.ts +74 -0
- package/src/router.ts +31 -0
- package/src/search-engine/bing.ts +32 -0
- package/src/search-engine/google-jwt.ts +95 -0
- package/src/search-engine/google.ts +139 -0
- package/src/search-engine/index-now.ts +64 -0
- package/src/search-engine/index.ts +12 -0
- package/src/security/index.ts +230 -0
- package/src/server/index.ts +286 -0
- package/src/server-context/browser.ts +113 -0
- package/src/server-context/index.ts +217 -0
- package/src/server-fn/index.ts +710 -0
- package/src/server-fn-query.ts +169 -0
- package/src/server-handler/etag.ts +51 -0
- package/src/server-handler/headers.ts +5 -0
- package/src/server-handler/index.ts +1664 -0
- package/src/server-handler/types.ts +24 -0
- package/src/server-handler/validate-static-params.ts +130 -0
- package/src/server.ts +2 -0
- package/src/service-worker/template.ts +153 -0
- package/src/service-worker-hook/index.ts +70 -0
- package/src/sitemap/index.ts +231 -0
- package/src/ssr/critical-css.ts +150 -0
- package/src/ssr/head-prefix.ts +65 -0
- package/src/ssr/head.ts +259 -0
- package/src/ssr/index.tsx +879 -0
- package/src/state-parser/index.ts +266 -0
- package/src/store/filesystem.ts +131 -0
- package/src/store/index.ts +29 -0
- package/src/styles/index.ts +1200 -0
- package/src/styles/sx-types.ts +11 -0
- package/src/testing/index.ts +251 -0
- package/src/testing.ts +2 -0
- package/src/theme/index.tsx +232 -0
- package/src/theme.ts +3 -0
- package/src/tracing/index.ts +4 -0
- package/src/tracing/noop.ts +13 -0
- package/src/tracing/otel.ts +37 -0
- package/src/tracing/timing.ts +34 -0
- package/src/tracing/types.ts +9 -0
- package/src/url/index.ts +140 -0
- package/src/use-router.ts +11 -0
- package/src/use-suspense-query.ts +2 -0
- package/src/validation/index.ts +57 -0
- package/src/view-transition-css.ts +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FlareTracer } from "../tracing/types.ts";
|
|
2
|
+
|
|
3
|
+
export interface TracingConfig {
|
|
4
|
+
provider?: FlareTracer;
|
|
5
|
+
timing?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CspDirectives {
|
|
9
|
+
"base-uri"?: string[];
|
|
10
|
+
"connect-src"?: string[];
|
|
11
|
+
"default-src"?: string[];
|
|
12
|
+
"font-src"?: string[];
|
|
13
|
+
"form-action"?: string[];
|
|
14
|
+
"frame-ancestors"?: string[];
|
|
15
|
+
"frame-src"?: string[];
|
|
16
|
+
"img-src"?: string[];
|
|
17
|
+
"media-src"?: string[];
|
|
18
|
+
"object-src"?: string[];
|
|
19
|
+
"script-src"?: string[];
|
|
20
|
+
"style-src"?: string[];
|
|
21
|
+
"block-all-mixed-content"?: boolean;
|
|
22
|
+
"upgrade-insecure-requests"?: boolean;
|
|
23
|
+
"worker-src"?: string[];
|
|
24
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ResolvedRoute } from "../loader-pipeline/index.ts";
|
|
2
|
+
import type { CacheConfig } from "../route-builder/types.ts";
|
|
3
|
+
|
|
4
|
+
type ParamsFn = (ctx: { params: Record<string, string | string[]> }) => unknown;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract params fn from a route's cache config.
|
|
8
|
+
* Handles: `ssg: fn`, `ssg: { params: fn }`, `isr: { params: fn }`
|
|
9
|
+
*/
|
|
10
|
+
export function extractParamsFn(cache: CacheConfig | undefined): ParamsFn | undefined {
|
|
11
|
+
if (!cache) return undefined;
|
|
12
|
+
|
|
13
|
+
const ssg = cache.ssg;
|
|
14
|
+
if (typeof ssg === "function") return ssg as ParamsFn;
|
|
15
|
+
if (ssg && typeof ssg === "object") {
|
|
16
|
+
const params = (ssg as Record<string, unknown>).params;
|
|
17
|
+
if (typeof params === "function") return params as ParamsFn;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isr = cache.isr;
|
|
21
|
+
if (isr && typeof isr === "object") {
|
|
22
|
+
const params = (isr as Record<string, unknown>).params;
|
|
23
|
+
if (typeof params === "function") return params as ParamsFn;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Determine whether a cache config requires strict param validation.
|
|
31
|
+
* - ssg with params fn → always validate
|
|
32
|
+
* - isr with params fn + dynamicParams: false → validate
|
|
33
|
+
* - everything else → skip
|
|
34
|
+
*/
|
|
35
|
+
function shouldValidateParams(cache: CacheConfig | undefined): boolean {
|
|
36
|
+
if (!cache) return false;
|
|
37
|
+
|
|
38
|
+
const ssg = cache.ssg;
|
|
39
|
+
if (typeof ssg === "function") return true;
|
|
40
|
+
if (ssg && typeof ssg === "object" && "params" in ssg) return true;
|
|
41
|
+
|
|
42
|
+
const isr = cache.isr;
|
|
43
|
+
if (isr && typeof isr === "object" && "params" in isr) {
|
|
44
|
+
const dynamicParams = (isr as Record<string, unknown>).dynamicParams ?? true;
|
|
45
|
+
return dynamicParams === false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* Production cache: caches params fn results per virtualPath+matchParams.
|
|
53
|
+
* Skipped in dev mode so route file edits take effect without restart —
|
|
54
|
+
* layout modules are loaded via dynamic imports that Vite invalidates on
|
|
55
|
+
* file change, but this module's state persists across those invalidations.
|
|
56
|
+
*
|
|
57
|
+
* Bounded to MAX_PARAMS_CACHE_SIZE with FIFO eviction to prevent unbounded
|
|
58
|
+
* memory growth in production with many unique URL param combinations.
|
|
59
|
+
*/
|
|
60
|
+
const MAX_PARAMS_CACHE_SIZE = 500;
|
|
61
|
+
const paramsCache = new Map<string, Record<string, string | string[]>[]>();
|
|
62
|
+
|
|
63
|
+
export function clearParamsCache(): void {
|
|
64
|
+
paramsCache.clear();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate that the matched URL params are within the allowed set
|
|
69
|
+
* defined by SSG/ISR params functions in the route chain.
|
|
70
|
+
*
|
|
71
|
+
* Returns `false` if any route in the chain rejects the params.
|
|
72
|
+
*
|
|
73
|
+
* In dev mode, params fn results are NOT cached so edits to route files
|
|
74
|
+
* (e.g. changing params lists) take effect immediately without restart.
|
|
75
|
+
*/
|
|
76
|
+
export async function validateStaticParams(
|
|
77
|
+
resolvedRoutes: ResolvedRoute[],
|
|
78
|
+
matchParams: Record<string, string | string[]>,
|
|
79
|
+
isDev?: boolean,
|
|
80
|
+
): Promise<boolean> {
|
|
81
|
+
for (const route of resolvedRoutes) {
|
|
82
|
+
if (!shouldValidateParams(route.cache)) continue;
|
|
83
|
+
|
|
84
|
+
const paramsFn = extractParamsFn(route.cache);
|
|
85
|
+
if (!paramsFn) continue;
|
|
86
|
+
|
|
87
|
+
/* Cache key includes matchParams because params fns can depend on parent
|
|
88
|
+
* params (e.g. repoSegment reads ctx.params.org to filter repos). */
|
|
89
|
+
const cacheKey = `${route.virtualPath}:${JSON.stringify(matchParams, Object.keys(matchParams).sort())}`;
|
|
90
|
+
let allowedList = isDev ? undefined : paramsCache.get(cacheKey);
|
|
91
|
+
if (!allowedList) {
|
|
92
|
+
const produced = await paramsFn({ params: matchParams });
|
|
93
|
+
allowedList = Array.isArray(produced) ? (produced as Record<string, string | string[]>[]) : [];
|
|
94
|
+
if (!isDev) {
|
|
95
|
+
if (paramsCache.size >= MAX_PARAMS_CACHE_SIZE) {
|
|
96
|
+
const oldest = paramsCache.keys().next().value;
|
|
97
|
+
if (oldest !== undefined) paramsCache.delete(oldest);
|
|
98
|
+
}
|
|
99
|
+
paramsCache.set(cacheKey, allowedList);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (allowedList.length === 0) return false;
|
|
104
|
+
|
|
105
|
+
/* Extract which param keys this level defines */
|
|
106
|
+
const paramKeys = Object.keys(allowedList[0] ?? {});
|
|
107
|
+
if (paramKeys.length === 0) continue;
|
|
108
|
+
|
|
109
|
+
/* Check if matchParams values exist in the allowed list */
|
|
110
|
+
const matched = allowedList.some((allowed) =>
|
|
111
|
+
paramKeys.every((key) => {
|
|
112
|
+
const matchVal = matchParams[key];
|
|
113
|
+
const allowedVal = allowed[key];
|
|
114
|
+
/* Optional param [[x]] skipped — not in matchParams → skip validation */
|
|
115
|
+
if (matchVal === undefined) return true;
|
|
116
|
+
if (allowedVal === undefined) return false;
|
|
117
|
+
if (Array.isArray(matchVal) && Array.isArray(allowedVal)) {
|
|
118
|
+
if (matchVal.length !== allowedVal.length) return false;
|
|
119
|
+
return matchVal.every((v, i) => v === allowedVal[i]);
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(matchVal) || Array.isArray(allowedVal)) return false;
|
|
122
|
+
return String(matchVal) === String(allowedVal);
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!matched) return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
export interface SwTemplateConfig {
|
|
2
|
+
/** URL prefix for cache-first asset matching. Defaults to `"/assets"`. */
|
|
3
|
+
assetsBase?: string;
|
|
4
|
+
offlineFallback: string | null;
|
|
5
|
+
runtimeCacheMax: number;
|
|
6
|
+
skipWaiting: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function generateSwSource(precacheManifest: string[], buildId: string, config: SwTemplateConfig): string {
|
|
10
|
+
const manifest = JSON.stringify(precacheManifest);
|
|
11
|
+
const assetsBase = config.assetsBase ?? "/assets";
|
|
12
|
+
const swConfig = JSON.stringify({
|
|
13
|
+
assetsBase,
|
|
14
|
+
offlineFallback: config.offlineFallback,
|
|
15
|
+
runtimeCacheMax: config.runtimeCacheMax,
|
|
16
|
+
skipWaiting: config.skipWaiting,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return `/* Flare Service Worker — generated at build time */
|
|
20
|
+
var PRECACHE_MANIFEST = ${manifest}
|
|
21
|
+
var SW_CONFIG = ${swConfig}
|
|
22
|
+
var BUILD_ID = "${buildId}"
|
|
23
|
+
var CACHE_NAME = "flare-assets-${buildId}"
|
|
24
|
+
var RUNTIME_CACHE = "flare-runtime-v1"
|
|
25
|
+
|
|
26
|
+
/* ── Install ─────────────────────────────────────────────────────── */
|
|
27
|
+
|
|
28
|
+
self.addEventListener("install", function (event) {
|
|
29
|
+
event.waitUntil(
|
|
30
|
+
caches.open(CACHE_NAME).then(function (cache) {
|
|
31
|
+
var promises = [cache.addAll(PRECACHE_MANIFEST)]
|
|
32
|
+
if (SW_CONFIG.offlineFallback) {
|
|
33
|
+
promises.push(
|
|
34
|
+
caches.open(RUNTIME_CACHE).then(function (rtCache) {
|
|
35
|
+
return fetch(SW_CONFIG.offlineFallback).then(function (res) {
|
|
36
|
+
if (res.ok) return rtCache.put(SW_CONFIG.offlineFallback, res)
|
|
37
|
+
}).catch(function () {})
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return Promise.all(promises)
|
|
42
|
+
}).then(function () {
|
|
43
|
+
if (SW_CONFIG.skipWaiting) self.skipWaiting()
|
|
44
|
+
}).catch(function (err) {
|
|
45
|
+
/* Install failed — old SW stays active. Log for debugging. */
|
|
46
|
+
console.error("[flare-sw] install failed:", err)
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/* ── Activate ────────────────────────────────────────────────────── */
|
|
52
|
+
|
|
53
|
+
self.addEventListener("activate", function (event) {
|
|
54
|
+
event.waitUntil(
|
|
55
|
+
Promise.all([
|
|
56
|
+
self.registration.navigationPreload && self.registration.navigationPreload.enable(),
|
|
57
|
+
caches.keys().then(function (names) {
|
|
58
|
+
return Promise.all(
|
|
59
|
+
names.filter(function (n) {
|
|
60
|
+
return n.startsWith("flare-assets-") && n !== CACHE_NAME
|
|
61
|
+
}).map(function (n) {
|
|
62
|
+
return caches.delete(n)
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
}),
|
|
66
|
+
self.clients.claim(),
|
|
67
|
+
])
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/* ── Message ─────────────────────────────────────────────────────── */
|
|
72
|
+
|
|
73
|
+
self.addEventListener("message", function (event) {
|
|
74
|
+
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
75
|
+
self.skipWaiting()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/* ── Fetch ───────────────────────────────────────────────────────── */
|
|
80
|
+
|
|
81
|
+
self.addEventListener("fetch", function (event) {
|
|
82
|
+
var url = new URL(event.request.url)
|
|
83
|
+
|
|
84
|
+
/* Only handle same-origin */
|
|
85
|
+
if (url.origin !== self.location.origin) return
|
|
86
|
+
|
|
87
|
+
/* Passthrough: NDJSON (x-d header), server functions, keepalive */
|
|
88
|
+
if (event.request.headers.has("x-d")) return
|
|
89
|
+
if (url.pathname.startsWith("/_fn/")) return
|
|
90
|
+
if (url.pathname === "/_flare/keepalive") return
|
|
91
|
+
|
|
92
|
+
/* Cache-first for hashed assets under the configured assetsBase */
|
|
93
|
+
if (url.pathname.startsWith(${JSON.stringify(`${assetsBase}/`)})) {
|
|
94
|
+
event.respondWith(
|
|
95
|
+
caches.match(event.request).then(function (cached) {
|
|
96
|
+
if (cached) return cached
|
|
97
|
+
return fetch(event.request).then(function (response) {
|
|
98
|
+
if (response.ok) {
|
|
99
|
+
var clone = response.clone()
|
|
100
|
+
caches.open(CACHE_NAME).then(function (cache) {
|
|
101
|
+
cache.put(event.request, clone)
|
|
102
|
+
}).catch(function () {})
|
|
103
|
+
}
|
|
104
|
+
return response
|
|
105
|
+
})
|
|
106
|
+
}).catch(function () {
|
|
107
|
+
/* Cache API error — fall through to network */
|
|
108
|
+
return fetch(event.request)
|
|
109
|
+
})
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Network-first for navigations */
|
|
115
|
+
if (event.request.mode === "navigate") {
|
|
116
|
+
event.respondWith(
|
|
117
|
+
(function () {
|
|
118
|
+
var preload = event.preloadResponse || Promise.resolve(undefined)
|
|
119
|
+
return preload.then(function (preloaded) {
|
|
120
|
+
if (preloaded) return preloaded
|
|
121
|
+
return fetch(event.request)
|
|
122
|
+
}).then(function (response) {
|
|
123
|
+
if (response.ok) {
|
|
124
|
+
var clone = response.clone()
|
|
125
|
+
caches.open(RUNTIME_CACHE).then(function (cache) {
|
|
126
|
+
cache.put(event.request, clone).catch(function () {})
|
|
127
|
+
/* LRU eviction — batch delete to stay under limit */
|
|
128
|
+
cache.keys().then(function (keys) {
|
|
129
|
+
var i = 0
|
|
130
|
+
while (keys.length - i > SW_CONFIG.runtimeCacheMax) {
|
|
131
|
+
cache.delete(keys[i])
|
|
132
|
+
i++
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}).catch(function () {})
|
|
136
|
+
}
|
|
137
|
+
return response
|
|
138
|
+
}).catch(function () {
|
|
139
|
+
return caches.match(event.request).then(function (cached) {
|
|
140
|
+
if (cached) return cached
|
|
141
|
+
if (SW_CONFIG.offlineFallback) {
|
|
142
|
+
return caches.match(SW_CONFIG.offlineFallback)
|
|
143
|
+
}
|
|
144
|
+
return undefined
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})()
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createSignal, onCleanup } from "solid-js";
|
|
2
|
+
|
|
3
|
+
export interface ServiceWorkerState {
|
|
4
|
+
update: () => void;
|
|
5
|
+
updateAvailable: () => boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useServiceWorker(): ServiceWorkerState {
|
|
9
|
+
const [updateAvailable, setUpdateAvailable] = createSignal(false);
|
|
10
|
+
let waitingWorker: ServiceWorker | null = null;
|
|
11
|
+
|
|
12
|
+
if ("serviceWorker" in navigator) {
|
|
13
|
+
const sw = navigator.serviceWorker;
|
|
14
|
+
const trackedListeners: Array<{
|
|
15
|
+
handler: () => void;
|
|
16
|
+
name: string;
|
|
17
|
+
target: { removeEventListener: (name: string, handler: () => void) => void };
|
|
18
|
+
}> = [];
|
|
19
|
+
|
|
20
|
+
const onControllerChange = () => {
|
|
21
|
+
window.location.reload();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
sw.addEventListener("controllerchange", onControllerChange);
|
|
25
|
+
|
|
26
|
+
onCleanup(() => {
|
|
27
|
+
sw.removeEventListener("controllerchange", onControllerChange);
|
|
28
|
+
for (const entry of trackedListeners) {
|
|
29
|
+
entry.target.removeEventListener(entry.name, entry.handler);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
sw.ready.then((registration) => {
|
|
34
|
+
const checkWaiting = (worker: ServiceWorker | null) => {
|
|
35
|
+
if (!worker) return;
|
|
36
|
+
if (worker.state === "installed") {
|
|
37
|
+
waitingWorker = worker;
|
|
38
|
+
setUpdateAvailable(true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const handler = () => {
|
|
42
|
+
if (worker.state === "installed") {
|
|
43
|
+
waitingWorker = worker;
|
|
44
|
+
setUpdateAvailable(true);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
worker.addEventListener("statechange", handler);
|
|
48
|
+
trackedListeners.push({ handler, name: "statechange", target: worker });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
checkWaiting(registration.waiting);
|
|
52
|
+
checkWaiting(registration.installing);
|
|
53
|
+
|
|
54
|
+
const onUpdateFound = () => {
|
|
55
|
+
checkWaiting(registration.installing);
|
|
56
|
+
};
|
|
57
|
+
registration.addEventListener("updatefound", onUpdateFound);
|
|
58
|
+
trackedListeners.push({ handler: onUpdateFound, name: "updatefound", target: registration });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
update() {
|
|
64
|
+
if (waitingWorker) {
|
|
65
|
+
waitingWorker.postMessage({ type: "SKIP_WAITING" });
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
updateAvailable,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { RouteDefinition } from "../generators/index.ts";
|
|
2
|
+
|
|
3
|
+
/* ── Types ────────────────────────────────────────────────────────────── */
|
|
4
|
+
|
|
5
|
+
export type ChangeFreq = "always" | "daily" | "hourly" | "monthly" | "never" | "weekly" | "yearly";
|
|
6
|
+
|
|
7
|
+
export interface SitemapEntry {
|
|
8
|
+
changefreq?: ChangeFreq;
|
|
9
|
+
lastmod?: string;
|
|
10
|
+
loc: string;
|
|
11
|
+
priority?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SitemapConfig {
|
|
15
|
+
additionalEntries?: SitemapEntry[];
|
|
16
|
+
changefreq?: ChangeFreq | Record<string, ChangeFreq>;
|
|
17
|
+
exclude?: string[];
|
|
18
|
+
origin: string;
|
|
19
|
+
priority?: number | Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SitemapResult {
|
|
23
|
+
files: Array<{ content: string; path: string }>;
|
|
24
|
+
urls: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ── Glob matching ────────────────────────────────────────────────────── */
|
|
28
|
+
|
|
29
|
+
function globToRegex(pattern: string): RegExp {
|
|
30
|
+
let result = "^";
|
|
31
|
+
let i = 0;
|
|
32
|
+
while (i < pattern.length) {
|
|
33
|
+
if (pattern[i] === "*" && pattern[i + 1] === "*") {
|
|
34
|
+
result += ".*";
|
|
35
|
+
i += 2;
|
|
36
|
+
if (pattern[i] === "/") i++;
|
|
37
|
+
} else if (pattern[i] === "*") {
|
|
38
|
+
result += "[^/]*";
|
|
39
|
+
i++;
|
|
40
|
+
} else if (".+?^${}()|[]\\".includes(pattern[i] ?? "")) {
|
|
41
|
+
result += `\\${pattern[i]}`;
|
|
42
|
+
i++;
|
|
43
|
+
} else {
|
|
44
|
+
result += pattern[i];
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
result += "$";
|
|
49
|
+
return new RegExp(result);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── URL computation (mirrors generators/index.ts computeUrlPattern) ── */
|
|
53
|
+
|
|
54
|
+
function computeUrlPattern(virtualPath: string): string {
|
|
55
|
+
const parts = virtualPath.split("/");
|
|
56
|
+
const rootIdx = parts.findIndex((p) => p.startsWith("_") && p.endsWith("_") && p.length >= 3);
|
|
57
|
+
const urlParts: string[] = [];
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < parts.length; i++) {
|
|
60
|
+
const part = parts[i] ?? "";
|
|
61
|
+
if (rootIdx >= 0 && i <= rootIdx) continue;
|
|
62
|
+
if (part.startsWith("(") && part.endsWith(")")) continue;
|
|
63
|
+
if (part === "") continue;
|
|
64
|
+
urlParts.push(part);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (urlParts.length === 0) return "/";
|
|
68
|
+
return `/${urlParts.join("/")}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ── Dynamic param detection ──────────────────────────────────────────── */
|
|
72
|
+
|
|
73
|
+
const DYNAMIC_PARAM_RE = /\[.*\]/;
|
|
74
|
+
|
|
75
|
+
function hasDynamicParams(virtualPath: string): boolean {
|
|
76
|
+
return DYNAMIC_PARAM_RE.test(virtualPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ── Route filtering ──────────────────────────────────────────────────── */
|
|
80
|
+
|
|
81
|
+
export function filterSitemapRoutes(defs: RouteDefinition[], exclude?: string[]): RouteDefinition[] {
|
|
82
|
+
const excludePatterns = exclude?.map(globToRegex) ?? [];
|
|
83
|
+
|
|
84
|
+
return defs.filter((def) => {
|
|
85
|
+
if (def.type !== "page") return false;
|
|
86
|
+
if (def.responseRoute) return false;
|
|
87
|
+
if (def.authenticateMode === true) return false;
|
|
88
|
+
if (hasDynamicParams(def.virtualPath)) return false;
|
|
89
|
+
|
|
90
|
+
const url = computeUrlPattern(def.virtualPath);
|
|
91
|
+
for (const pattern of excludePatterns) {
|
|
92
|
+
if (pattern.test(url)) return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ── Entry building ───────────────────────────────────────────────────── */
|
|
100
|
+
|
|
101
|
+
function resolveOverride<T>(url: string, value: T | Record<string, T> | undefined): T | undefined {
|
|
102
|
+
if (value === undefined) return undefined;
|
|
103
|
+
if (typeof value !== "object" || value === null) return value as T;
|
|
104
|
+
|
|
105
|
+
const overrides = value as Record<string, T>;
|
|
106
|
+
let bestMatch: { key: string; value: T } | undefined;
|
|
107
|
+
|
|
108
|
+
for (const [pattern, val] of Object.entries(overrides)) {
|
|
109
|
+
const re = globToRegex(pattern);
|
|
110
|
+
if (re.test(url)) {
|
|
111
|
+
if (!bestMatch || pattern.length > bestMatch.key.length) {
|
|
112
|
+
bestMatch = { key: pattern, value: val };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return bestMatch?.value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildSitemapEntries(defs: RouteDefinition[], config: SitemapConfig): SitemapEntry[] {
|
|
121
|
+
const entries: SitemapEntry[] = [];
|
|
122
|
+
const origin = config.origin.replace(/\/+$/, "");
|
|
123
|
+
|
|
124
|
+
for (const def of defs) {
|
|
125
|
+
const url = computeUrlPattern(def.virtualPath);
|
|
126
|
+
const entry: SitemapEntry = { loc: `${origin}${url}` };
|
|
127
|
+
|
|
128
|
+
const freq = resolveOverride(url, config.changefreq);
|
|
129
|
+
if (freq) entry.changefreq = freq;
|
|
130
|
+
|
|
131
|
+
const prio = resolveOverride(url, config.priority);
|
|
132
|
+
if (prio !== undefined) entry.priority = prio;
|
|
133
|
+
|
|
134
|
+
entries.push(entry);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (config.additionalEntries) {
|
|
138
|
+
entries.push(...config.additionalEntries);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return entries;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ── XML generation ───────────────────────────────────────────────────── */
|
|
145
|
+
|
|
146
|
+
function escapeXml(str: string): string {
|
|
147
|
+
return str
|
|
148
|
+
.replace(/&/g, "&")
|
|
149
|
+
.replace(/</g, "<")
|
|
150
|
+
.replace(/>/g, ">")
|
|
151
|
+
.replace(/"/g, """)
|
|
152
|
+
.replace(/'/g, "'");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function generateSitemapXml(entries: SitemapEntry[]): string {
|
|
156
|
+
const lines = [
|
|
157
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
158
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
lines.push(" <url>");
|
|
163
|
+
lines.push(` <loc>${escapeXml(entry.loc)}</loc>`);
|
|
164
|
+
if (entry.lastmod) lines.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`);
|
|
165
|
+
if (entry.changefreq) lines.push(` <changefreq>${escapeXml(entry.changefreq)}</changefreq>`);
|
|
166
|
+
if (entry.priority !== undefined) lines.push(` <priority>${entry.priority}</priority>`);
|
|
167
|
+
lines.push(" </url>");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lines.push("</urlset>");
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function generateSitemapIndexXml(origin: string, count: number): string {
|
|
175
|
+
const lines = [
|
|
176
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
177
|
+
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < count; i++) {
|
|
181
|
+
lines.push(" <sitemap>");
|
|
182
|
+
lines.push(` <loc>${escapeXml(origin)}/sitemap-${i}.xml</loc>`);
|
|
183
|
+
lines.push(" </sitemap>");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
lines.push("</sitemapindex>");
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ── Full pipeline ────────────────────────────────────────────────────── */
|
|
191
|
+
|
|
192
|
+
const MAX_SITEMAP_ENTRIES = 50_000;
|
|
193
|
+
|
|
194
|
+
export function generateSitemap(config: SitemapConfig, entries: SitemapEntry[]): SitemapResult {
|
|
195
|
+
const urls = entries.map((e) => e.loc);
|
|
196
|
+
|
|
197
|
+
if (entries.length <= MAX_SITEMAP_ENTRIES) {
|
|
198
|
+
return {
|
|
199
|
+
files: [{ content: generateSitemapXml(entries), path: "sitemap.xml" }],
|
|
200
|
+
urls,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const files: Array<{ content: string; path: string }> = [];
|
|
205
|
+
const partCount = Math.ceil(entries.length / MAX_SITEMAP_ENTRIES);
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < partCount; i++) {
|
|
208
|
+
const start = i * MAX_SITEMAP_ENTRIES;
|
|
209
|
+
const chunk = entries.slice(start, start + MAX_SITEMAP_ENTRIES);
|
|
210
|
+
files.push({ content: generateSitemapXml(chunk), path: `sitemap-${i}.xml` });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
files.push({ content: generateSitemapIndexXml(config.origin, partCount), path: "sitemap.xml" });
|
|
214
|
+
|
|
215
|
+
return { files, urls };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ── robots.txt ───────────────────────────────────────────────────────── */
|
|
219
|
+
|
|
220
|
+
export function generateRobotsTxt(sitemapUrl: string, rules?: string): string {
|
|
221
|
+
const base = rules ?? "User-agent: *\nAllow: /";
|
|
222
|
+
return `${base}\n\nSitemap: ${sitemapUrl}\n`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ── Convenience: filter + build + generate ───────────────────────────── */
|
|
226
|
+
|
|
227
|
+
export function buildSitemapFromDefs(defs: RouteDefinition[], config: SitemapConfig): SitemapResult {
|
|
228
|
+
const filtered = filterSitemapRoutes(defs, config.exclude);
|
|
229
|
+
const entries = buildSitemapEntries(filtered, config);
|
|
230
|
+
return generateSitemap(config, entries);
|
|
231
|
+
}
|