@binsarjr/sveltekit-sitemap
Advanced tools
Comparing version 0.0.22 to 0.1.0
@@ -1,3 +0,3 @@ | ||
import type { Handle } from "@sveltejs/kit"; | ||
import type { SitemapParams } from "./types.ts"; | ||
import type { Handle } from '@sveltejs/kit'; | ||
import type { SitemapParams } from './types.ts'; | ||
export declare const sitemapHook: <S extends import("type-fest/source/readonly-deep").ReadonlyObjectDeep<import("./types.ts").Sitemap>>(sitemap: S, params?: SitemapParams<S> | undefined) => Handle; |
@@ -1,4 +0,4 @@ | ||
import { generateRobots, generateSitemap } from "./utils.js"; | ||
import { generateRobots, generateSitemap } from './utils.js'; | ||
export const sitemapHook = (sitemap, params = {}) => async ({ event, resolve }) => { | ||
if (event.url.pathname === "/sitemap.xml") { | ||
if (event.url.pathname === '/sitemap.xml') { | ||
// Get dynamic custom definition for app routes | ||
@@ -9,7 +9,7 @@ const routeDefinitions = params.getRoutes ? await params.getRoutes(event) : {}; | ||
headers: { | ||
"Content-Type": "application/xml" | ||
'Content-Type': 'application/xml' | ||
} | ||
}); | ||
} | ||
if (event.url.pathname === "/robots.txt") { | ||
if (event.url.pathname === '/robots.txt') { | ||
// Get dynamic robots directives | ||
@@ -20,5 +20,5 @@ const robots = params.getRobots ? await params.getRobots(event) : true; | ||
headers: { | ||
"content-type": "text/plain", | ||
'content-type': 'text/plain', | ||
// Cache it for 24 hours | ||
"cache-control": `max-age=${60 * 60 * 24}` | ||
'cache-control': `max-age=${60 * 60 * 24}` | ||
} | ||
@@ -25,0 +25,0 @@ }); |
@@ -1,3 +0,3 @@ | ||
import type { ViteDevServer } from "vite"; | ||
import type { SitemapPluginParams } from "./types.ts"; | ||
import type { ViteDevServer } from 'vite'; | ||
import type { SitemapPluginParams } from './types.ts'; | ||
export declare const sitemapPlugin: ({ routesDir, sitemapFile }?: SitemapPluginParams) => { | ||
@@ -4,0 +4,0 @@ name: string; |
@@ -1,4 +0,4 @@ | ||
import fs from "fs"; | ||
import { getRoutes } from "./utils.js"; | ||
export const sitemapPlugin = ({ routesDir = "./src/routes", sitemapFile = "./src/sitemap.ts" } = {}) => { | ||
import fs from 'fs'; | ||
import { getRoutes } from './utils.js'; | ||
export const sitemapPlugin = ({ routesDir = './src/routes', sitemapFile = './src/sitemap.ts' } = {}) => { | ||
function updateSitemap() { | ||
@@ -22,11 +22,11 @@ if (/\.ts$/i.test(sitemapFile)) { | ||
return { | ||
name: "sveltekit-sitemap", | ||
name: 'sveltekit-sitemap', | ||
configureServer(server) { | ||
server.watcher | ||
.add([routesDir]) | ||
.on("add", updateSitemap) | ||
.on("unlink", updateSitemap) | ||
.on("unlinkDir", updateSitemap); | ||
.on('add', updateSitemap) | ||
.on('unlink', updateSitemap) | ||
.on('unlinkDir', updateSitemap); | ||
} | ||
}; | ||
}; |
@@ -1,3 +0,3 @@ | ||
import type { RequestEvent } from "@sveltejs/kit"; | ||
import type { ReadonlyDeep, SetOptional } from "type-fest"; | ||
import type { RequestEvent } from '@sveltejs/kit'; | ||
import type { ReadonlyDeep, SetOptional } from 'type-fest'; | ||
export type RO_Sitemap = ReadonlyDeep<Sitemap>; | ||
@@ -9,3 +9,3 @@ export type Sitemap = Record<string, boolean>; | ||
export type Folders<S extends RO_Sitemap> = Str<{ | ||
[K in keyof S]: S[K] extends true ? (K extends string ? (K extends "/" ? "/" : `${K}/`) : never) : never; | ||
[K in keyof S]: S[K] extends true ? K extends string ? K extends '/' ? '/' : `${K}/` : never : never; | ||
}[keyof S]>; | ||
@@ -19,4 +19,4 @@ /** | ||
export type StaticRoutes<S extends RO_Sitemap, R extends Routes<S> = Routes<S>> = Str<R extends `${infer Prefix}[${infer _}]${infer Suffix}` ? never : R>; | ||
export type Priority = "1.0" | "0.9" | "0.8" | "0.7" | "0.6" | "0.5" | "0.4" | "0.3" | "0.2" | "0.1" | "0.0"; | ||
export type Frequency = "Always" | "Hourly" | "Weekly" | "Monthly" | "Yearly" | "Never"; | ||
export type Priority = '1.0' | '0.9' | '0.8' | '0.7' | '0.6' | '0.5' | '0.4' | '0.3' | '0.2' | '0.1' | '0.0'; | ||
export type Frequency = 'Always' | 'Hourly' | 'Weekly' | 'Monthly' | 'Yearly' | 'Never'; | ||
export type RouteDefinition<S extends boolean> = SetOptional<{ | ||
@@ -52,3 +52,3 @@ path: string; | ||
image?: RouteDefinitionImage; | ||
}, S extends true ? "path" : never>; | ||
}, S extends true ? 'path' : never>; | ||
export type RouteDefinitionImage = { | ||
@@ -61,3 +61,3 @@ url: string; | ||
export type PathDirectives<S extends Sitemap> = { | ||
[K in Routes<S> | Folders<S> | "/$"]?: K extends DynamicRoutes<S> ? { | ||
[K in Routes<S> | Folders<S> | '/$']?: K extends DynamicRoutes<S> ? { | ||
[K in string]?: boolean; | ||
@@ -75,3 +75,3 @@ } : boolean; | ||
export type RouteDefinitions<S extends RO_Sitemap> = { | ||
[K in Routes<S>]?: K extends StaticRoutes<S> ? RouteDefinition<true> : RouteDefinition<false>[]; | ||
[K in Routes<S>]?: K extends StaticRoutes<S> ? RouteDefinition<true> | false : RouteDefinition<false>[]; | ||
}; | ||
@@ -86,2 +86,2 @@ export type SitemapParams<S extends RO_Sitemap> = { | ||
}; | ||
export type ReplaceParams<S extends string, Delimiter extends string = "/"> = S extends `${infer Head}${Delimiter}${infer Tail}` ? Head extends `[${infer P}]` ? `${string}/${ReplaceParams<Tail, Delimiter>}` : `${Head}/${ReplaceParams<Tail, Delimiter>}` : S extends Delimiter ? "" : S extends `[${infer P}]` ? string : `${S}`; | ||
export type ReplaceParams<S extends string, Delimiter extends string = '/'> = S extends `${infer Head}${Delimiter}${infer Tail}` ? Head extends `[${infer P}]` ? `${string}/${ReplaceParams<Tail, Delimiter>}` : `${Head}/${ReplaceParams<Tail, Delimiter>}` : S extends Delimiter ? '' : S extends `[${infer P}]` ? string : `${S}`; |
@@ -1,2 +0,2 @@ | ||
import type { RouteDefinitions, Sitemap, UserAgentDirective } from "./types.ts"; | ||
import type { RouteDefinitions, Sitemap, UserAgentDirective } from './types.ts'; | ||
export declare const encodeXML: (str: string) => string; | ||
@@ -3,0 +3,0 @@ export declare const generateSitemap: <S extends import("type-fest/source/readonly-deep").ReadonlyObjectDeep<Sitemap>>(definitions: RouteDefinitions<S>, baseUrl: string, sitemap: S) => string; |
@@ -1,9 +0,9 @@ | ||
import fs from "fs"; | ||
import fs from 'fs'; | ||
export const encodeXML = (str) => { | ||
return str | ||
.replace(/&/g, "&") | ||
.replace(/</g, "<") | ||
.replace(/>/g, ">") | ||
.replace(/"/g, """) | ||
.replace(/'/g, "'"); | ||
.replace(/&/g, '&') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
.replace(/"/g, '"') | ||
.replace(/'/g, '''); | ||
}; | ||
@@ -13,9 +13,12 @@ export const generateSitemap = (definitions, baseUrl, sitemap) => { | ||
// The will be override if you pass custom settings | ||
const routes = Object.keys(sitemap).reduce((acc, route) => { | ||
const isDynamic = route.includes("["); | ||
if (!isDynamic) { | ||
Object.assign(acc, { | ||
[route]: { path: route, priority: route === "/" ? "1.0" : "0.7" } | ||
}); | ||
} | ||
const routes = Object.keys(sitemap) | ||
// filter and get static route only for default | ||
.filter((sitemapKey) => sitemap[sitemapKey]) | ||
.reduce((acc, route) => { | ||
Object.assign(acc, { | ||
[route]: { | ||
path: route, | ||
priority: route === '/' ? '1.0' : '0.7' | ||
} | ||
}); | ||
return acc; | ||
@@ -26,2 +29,5 @@ }, {}); | ||
const RouteDefinition = definitions[route]; | ||
if (RouteDefinition === false) { | ||
delete routes[route]; | ||
} | ||
if (RouteDefinition) { | ||
@@ -55,5 +61,5 @@ if (Array.isArray(RouteDefinition)) { | ||
<loc>${baseUrl}${path || r}</loc> | ||
${lastMod ? `<lastmod>${lastMod}</lastmod>` : ""} | ||
${priority ? `<priority>${priority}</priority>` : ""} | ||
${changeFreq ? `<changefreq>${changeFreq}</changefreq>` : ""} | ||
${lastMod ? `<lastmod>${lastMod}</lastmod>` : ''} | ||
${priority ? `<priority>${priority}</priority>` : ''} | ||
${changeFreq ? `<changefreq>${changeFreq}</changefreq>` : ''} | ||
${image | ||
@@ -63,9 +69,9 @@ ? ` | ||
<image:loc>${encodeXML(image.url)}</image:loc> | ||
<image:title>${encodeXML(image.title ?? " ")}</image:title> | ||
<image:caption>${encodeXML(image.altText ?? " ")}</image:caption> | ||
<image:title>${encodeXML(image.title ?? ' ')}</image:title> | ||
<image:caption>${encodeXML(image.altText ?? ' ')}</image:caption> | ||
</image:image>` | ||
: ""} | ||
: ''} | ||
</url>`; | ||
}) | ||
.join("\n")} | ||
.join('\n')} | ||
</urlset>`; | ||
@@ -98,3 +104,3 @@ }; | ||
agentsToRender.push({ | ||
agent: agent.userAgent || "*", | ||
agent: agent.userAgent || '*', | ||
crawlDelay: agent.crawlDelay, | ||
@@ -106,7 +112,7 @@ ...infos | ||
// Build a default robot.txt for all user-agents | ||
if (typeof robots === "boolean") { | ||
if (typeof robots === 'boolean') { | ||
agentsToRender.push({ | ||
agent: "*", | ||
allow: robots === true ? ["/"] : [], | ||
disallow: robots === false ? ["/"] : [] | ||
agent: '*', | ||
allow: robots === true ? ['/'] : [], | ||
disallow: robots === false ? ['/'] : [] | ||
}); | ||
@@ -123,10 +129,10 @@ } | ||
Sitemap: ${baseUrl}/sitemap.xml | ||
${crawlDelay ? `Crawl-delay: ${crawlDelay}` : ""} | ||
${allow.map((route) => `Allow: ${route}`).join("\n")} | ||
${disallow.map((route) => `Disallow: ${route}`).join("\n")} | ||
${crawlDelay ? `Crawl-delay: ${crawlDelay}` : ''} | ||
${allow.map((route) => `Allow: ${route}`).join('\n')} | ||
${disallow.map((route) => `Disallow: ${route}`).join('\n')} | ||
` | ||
// Make it pretty | ||
.replace(/\n\n/g, "\n") | ||
.replace(/\n\n/g, "\n")) | ||
.join("\n")} | ||
.replace(/\n\n/g, '\n') | ||
.replace(/\n\n/g, '\n')) | ||
.join('\n')} | ||
`.trim(); | ||
@@ -137,6 +143,6 @@ }; | ||
const files = fs.readdirSync(path); | ||
if (files.some((file) => file === "+page.svelte")) | ||
if (files.some((file) => file === '+page.svelte')) | ||
return true; | ||
return files.some((file) => { | ||
const newPath = path + "/" + file; | ||
const newPath = path + '/' + file; | ||
if (fs.statSync(newPath).isDirectory()) { | ||
@@ -156,16 +162,17 @@ return hasPageInside(newPath); | ||
if (isDirectory && isPageFolder) { | ||
fs.readdirSync(path).forEach((file) => traverseRoutes(path + "/" + file)); | ||
fs.readdirSync(path).forEach((file) => traverseRoutes(path + '/' + file)); | ||
} | ||
const id = path.replace(dir, "").replace("/+page.svelte", ""); | ||
const dirBase = path.replace("/+page.svelte", ""); | ||
const id = path.replace(dir, '').replace('/+page.svelte', ''); | ||
const dirBase = path.replace('/+page.svelte', ''); | ||
const isFolder = fs.statSync(dirBase).isDirectory() && | ||
fs.readdirSync(path.replace("/+page.svelte", "")).some((p) => { | ||
return fs.statSync(dirBase + "/" + p).isDirectory(); | ||
fs.readdirSync(path.replace('/+page.svelte', '')).some((p) => { | ||
return fs.statSync(dirBase + '/' + p).isDirectory(); | ||
}); | ||
if (!path.endsWith("+page.svelte") && !isFolder) | ||
if (!path.endsWith('+page.svelte') && !isFolder) | ||
return; | ||
Object.assign(routes, { [id || "/"]: isFolder }); | ||
const staticRoute = !/\[[^\]]+\]/.test(id); | ||
Object.assign(routes, { [id || '/']: staticRoute }); | ||
}; | ||
fs.readdirSync(dir).forEach((file) => traverseRoutes(dir + "/" + file)); | ||
fs.readdirSync(dir).forEach((file) => traverseRoutes(dir + '/' + file)); | ||
return routes; | ||
}; |
{ | ||
"name": "@binsarjr/sveltekit-sitemap", | ||
"version": "0.0.22", | ||
"version": "0.1.0", | ||
"repository": { | ||
@@ -11,3 +11,3 @@ "url": "https://github.com/binsarjr/sveltekit-sitemap" | ||
"preview": "vite preview", | ||
"package": "svelte-kit sync && svelte-package && node afterBuild.cjs && publint", | ||
"package": "npm run format && npm run lint && svelte-kit sync && svelte-package && node afterBuild.cjs && publint", | ||
"prepublishOnly": "npm run package", | ||
@@ -64,2 +64,2 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", | ||
} | ||
} | ||
} |
166
README.md
# @binsarjr/sveltekit-sitemap (and robots) | ||
Recode from https://github.com/beynar/sveltekit-sitemap | ||
Recode from https://github.com/beynar/sveltekit-sitemap | ||
@@ -30,8 +30,8 @@ This library is designed to help generate and maintain dynamic sitemap.xml and robots.txt for SvelteKit apps. | ||
import { sveltekit } from "@sveltejs/kit/vite"; | ||
import { sitemapPlugin } from "@binsarjr/sveltekit-sitemap"; | ||
import { sveltekit } from '@sveltejs/kit/vite'; | ||
import { sitemapPlugin } from '@binsarjr/sveltekit-sitemap'; | ||
/** @type {import('vite').UserConfig} */ | ||
const config = { | ||
plugins: [sveltekit(), sitemapPlugin()] | ||
plugins: [sveltekit(), sitemapPlugin()] | ||
}; | ||
@@ -46,5 +46,5 @@ | ||
// src/hooks.server.ts | ||
import type { Handle } from "@sveltejs/kit"; | ||
import { sitemapHook } from "@binsarjr/sveltekit-sitemap"; | ||
import { sitemap } from "./sitemap"; | ||
import type { Handle } from '@sveltejs/kit'; | ||
import { sitemapHook } from '@binsarjr/sveltekit-sitemap'; | ||
import { sitemap } from './sitemap'; | ||
@@ -65,3 +65,3 @@ export const handle: Handle = sitemapHook(sitemap, params); | ||
```ts | ||
sitemapPlugin({ routesDir: "./src/routes", sitemapFile: "./src/sitemap.ts" }); | ||
sitemapPlugin({ routesDir: './src/routes', sitemapFile: './src/sitemap.ts' }); | ||
``` | ||
@@ -104,32 +104,32 @@ | ||
sitemapHook(sitemap, { | ||
//... | ||
getRoutes: async (event) => { | ||
const blogs = await event.locals.api.getBlogsForSitemap(); | ||
// ^-- make async api call to get fresh data | ||
//... | ||
getRoutes: async (event) => { | ||
const blogs = await event.locals.api.getBlogsForSitemap(); | ||
// ^-- make async api call to get fresh data | ||
return { | ||
"/about": { | ||
path: "/", | ||
priority: "0.8" | ||
}, | ||
// ^-- Static routes are automatically added to the sitemap. But if you want to customize them, you can return a route definition object. | ||
"blogs/[handle]": blogs, | ||
"/products/[id]": [ | ||
{ path: "/products/test-1" }, | ||
{ path: "/products/test-2" }, | ||
{ | ||
path: "/products/test-3", | ||
changeFreq: "Monthly", | ||
priority: "0.8", | ||
lastMod: "2023-01-01", | ||
image: { | ||
url: "https://picsum.photos/200/300", | ||
title: "test-1", | ||
altText: "image-product-test-1" | ||
} | ||
} | ||
] | ||
// ^-- For dynamic routes you have to return an array of route definitions | ||
}; | ||
} | ||
return { | ||
'/about': { | ||
path: '/', | ||
priority: '0.8' | ||
}, | ||
// ^-- Static routes are automatically added to the sitemap. But if you want to customize them, you can return a route definition object. | ||
'blogs/[handle]': blogs, | ||
'/products/[id]': [ | ||
{ path: '/products/test-1' }, | ||
{ path: '/products/test-2' }, | ||
{ | ||
path: '/products/test-3', | ||
changeFreq: 'Monthly', | ||
priority: '0.8', | ||
lastMod: '2023-01-01', | ||
image: { | ||
url: 'https://picsum.photos/200/300', | ||
title: 'test-1', | ||
altText: 'image-product-test-1' | ||
} | ||
} | ||
] | ||
// ^-- For dynamic routes you have to return an array of route definitions | ||
}; | ||
} | ||
}); | ||
@@ -164,8 +164,8 @@ ``` | ||
const directive: PathDirectives = { | ||
"/admin/": false, // the "/" after disables all routes under /admin | ||
"/blogs/": false, // all the "blogs" directory is disallow | ||
"/login": false, // disallow the login page | ||
"/blogs/[id]": { | ||
"/blogs/id": true // But the route blog/id is allowed | ||
} | ||
'/admin/': false, // the "/" after disables all routes under /admin | ||
'/blogs/': false, // all the "blogs" directory is disallow | ||
'/login': false, // disallow the login page | ||
'/blogs/[id]': { | ||
'/blogs/id': true // But the route blog/id is allowed | ||
} | ||
}; | ||
@@ -179,7 +179,7 @@ ``` | ||
sitemapHook(sitemap, { | ||
//... | ||
getRobots: async (event) => { | ||
const { isPreview } = event.locals; | ||
return isPreview ? false : true; | ||
} | ||
//... | ||
getRobots: async (event) => { | ||
const { isPreview } = event.locals; | ||
return isPreview ? false : true; | ||
} | ||
}); | ||
@@ -189,15 +189,15 @@ | ||
sitemapHook(sitemap, { | ||
//... | ||
getRobots: async (event) => { | ||
return { | ||
userAgent: ["*", "adsbot-google"], | ||
crawlDelay: 1000, | ||
paths: { | ||
"/account/": false, | ||
"/pages/[id]": { | ||
"/pages/preview": false | ||
} | ||
} | ||
}; | ||
} | ||
//... | ||
getRobots: async (event) => { | ||
return { | ||
userAgent: ['*', 'adsbot-google'], | ||
crawlDelay: 1000, | ||
paths: { | ||
'/account/': false, | ||
'/pages/[id]': { | ||
'/pages/preview': false | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
@@ -207,24 +207,24 @@ | ||
sitemapHook(sitemap, { | ||
//... | ||
getRobots: async (event) => { | ||
return [ | ||
{ | ||
userAgent: ["*", "adsbot-google"], | ||
crawlDelay: 1000, | ||
paths: { | ||
"/account/": false, | ||
"/pages/[id]": { | ||
"/pages/preview": false | ||
} | ||
} | ||
}, | ||
{ | ||
userAgent: "Googlebot-Image", | ||
paths: { | ||
"/": false | ||
} | ||
} | ||
]; | ||
} | ||
//... | ||
getRobots: async (event) => { | ||
return [ | ||
{ | ||
userAgent: ['*', 'adsbot-google'], | ||
crawlDelay: 1000, | ||
paths: { | ||
'/account/': false, | ||
'/pages/[id]': { | ||
'/pages/preview': false | ||
} | ||
} | ||
}, | ||
{ | ||
userAgent: 'Googlebot-Image', | ||
paths: { | ||
'/': false | ||
} | ||
} | ||
]; | ||
} | ||
}); | ||
``` | ||
``` |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
23280
329
223