From 8ea53d0b58aa9b33f39bd0d41fd5ecdf90d3f7ee Mon Sep 17 00:00:00 2001 From: Alex Tim Date: Thu, 16 Jun 2022 01:09:08 +0300 Subject: [PATCH] feat: createLinkInHead removed --- packages/integrations/sitemap/README.md | 40 +--- packages/integrations/sitemap/package.json | 3 +- .../sitemap/src/config-defaults.ts | 5 + .../integrations/sitemap/src/constants.ts | 10 +- .../sitemap/src/generate-sitemap.ts | 78 +++---- packages/integrations/sitemap/src/index.ts | 214 +++++++++--------- .../integrations/sitemap/src/process-pages.ts | 39 ---- packages/integrations/sitemap/src/schema.ts | 77 +++---- .../sitemap/src/utils/is-object-empty.ts | 14 +- .../sitemap/src/utils/is-valid-url.ts | 20 +- .../integrations/sitemap/src/utils/logger.ts | 64 +++--- .../sitemap/src/utils/parse-url.ts | 65 +++--- .../sitemap/src/validate-options.ts | 22 ++ .../integrations/sitemap/src/validate-opts.ts | 16 -- .../integrations/sitemap/src/with-options.ts | 20 -- pnpm-lock.yaml | 21 +- 16 files changed, 311 insertions(+), 397 deletions(-) create mode 100644 packages/integrations/sitemap/src/config-defaults.ts delete mode 100644 packages/integrations/sitemap/src/process-pages.ts create mode 100644 packages/integrations/sitemap/src/validate-options.ts delete mode 100644 packages/integrations/sitemap/src/validate-opts.ts delete mode 100644 packages/integrations/sitemap/src/with-options.ts diff --git a/packages/integrations/sitemap/README.md b/packages/integrations/sitemap/README.md index 601598a01..ab880a075 100644 --- a/packages/integrations/sitemap/README.md +++ b/packages/integrations/sitemap/README.md @@ -94,12 +94,6 @@ Generated sitemap content for two pages website: ``` -All pages generated during build will contain in `` section a link to sitemap: - -```html - -``` - You can also check our [Astro Integration Documentation][astro-integration] for more on integrations. ## Configuration @@ -164,26 +158,6 @@ export default { } ``` -### createLinkInHead - -`Boolean`, default is `true`, create a link on sitemap in `` section of generated pages. - -__astro.config.mjs__ - -```js -import sitemap from '@astrojs/sitemap'; - -export default { - site: 'https://stargazers.club', - integrations: [ - sitemap({ - // disable create links to sitemap in - createLinkInHead: false, - }), - ], -} -``` - ### changefreq, lastmod, priority `changefreq` - How frequently the page is likely to change. Available values: `always` \| `hourly` \| `daily` \| `weekly` \| `monthly` \| `yearly` \| `never`. @@ -197,7 +171,7 @@ export default { See detailed explanation of sitemap specific options on [sitemap.org](https://www.sitemaps.org/protocol.html). -:exclamation: This integration uses 'astro:build:done' hook. The hook exposes only generated page paths. So with present version of Astro the integration has no abilities to analyze a page source, frontmatter etc. The integration can add `changefreq`, `lastmod` and `priority` attributes only in a batch or nothing. +:exclamation: This integration uses 'astro:build:done' hook. The hook exposes generated page paths only. So with present version of Astro the integration has no abilities to analyze a page source, frontmatter etc. The integration can add `changefreq`, `lastmod` and `priority` attributes only in a batch or nothing. __astro.config.mjs__ @@ -210,7 +184,7 @@ export default { sitemap({ changefreq: 'weekly', priority: 0.7, - lastmod: new Date('2022-05-28'), + lastmod: new Date('2022-02-24'), }), ], } @@ -218,15 +192,17 @@ export default { ### serialize -Async or sync function called for each sitemap entry just before writing to disk. +Async or sync function called for each sitemap entry just before writing to a disk. -It receives as parameter `SitemapItem` object which consists of `url` (required, absolute URL of page) and optional `changefreq`, `lastmod`, `priority` and `links` properties. +It receives as parameter `SitemapItem` object which consists of `url` (required, absolute page URL) and optional `changefreq`, `lastmod`, `priority` and `links` properties. Optional `links` property contains a `LinkItem` list of alternate pages including a parent page. `LinkItem` type has two required fields: `url` (the fully-qualified URL for the version of this page for the specified language) and `hreflang` (a supported language code targeted by this version of the page). `serialize` function should return `SitemapItem`, touched or not. +The example below shows the ability to add the sitemap specific properties individually. + __astro.config.mjs__ ```js @@ -237,7 +213,7 @@ export default { integrations: [ sitemap({ serialize(item) { - if (/special-page/.test(item.url)) { + if (/your-special-page/.test(item.url)) { item.changefreq = 'daily'; item.lastmod = new Date(); item.priority = 0.9; @@ -251,7 +227,7 @@ export default { ### i18n -To localize sitemap you should supply the integration config with the `i18n` option. The integration will check generated page paths on presence of locale keys in paths. +To localize a sitemap you should supply the integration config with the `i18n` option. The integration will check generated page paths on presence of locale keys in paths. `i18n` object has two required properties: diff --git a/packages/integrations/sitemap/package.json b/packages/integrations/sitemap/package.json index 12b49ec4b..d61c608b3 100644 --- a/packages/integrations/sitemap/package.json +++ b/packages/integrations/sitemap/package.json @@ -31,9 +31,8 @@ "dev": "astro-scripts dev \"src/**/*.ts\"" }, "dependencies": { - "node-html-parser": "^5.3.3", "sitemap": "^7.1.1", - "zod": "^3.17.3" + "zod": "^3.17.3" }, "devDependencies": { "astro": "workspace:*", diff --git a/packages/integrations/sitemap/src/config-defaults.ts b/packages/integrations/sitemap/src/config-defaults.ts new file mode 100644 index 000000000..22288fc11 --- /dev/null +++ b/packages/integrations/sitemap/src/config-defaults.ts @@ -0,0 +1,5 @@ +import type { SitemapOptions } from './index'; + +export const SITEMAP_CONFIG_DEFAULTS: SitemapOptions & any = { + entryLimit: 45000, +}; diff --git a/packages/integrations/sitemap/src/constants.ts b/packages/integrations/sitemap/src/constants.ts index c983e1b1c..431cc5954 100644 --- a/packages/integrations/sitemap/src/constants.ts +++ b/packages/integrations/sitemap/src/constants.ts @@ -1 +1,9 @@ -export const changefreqValues = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'] as const; +export const changefreqValues = [ + 'always', + 'hourly', + 'daily', + 'weekly', + 'monthly', + 'yearly', + 'never', +] as const; diff --git a/packages/integrations/sitemap/src/generate-sitemap.ts b/packages/integrations/sitemap/src/generate-sitemap.ts index f2c2ea7ce..3c39e1f7e 100644 --- a/packages/integrations/sitemap/src/generate-sitemap.ts +++ b/packages/integrations/sitemap/src/generate-sitemap.ts @@ -7,49 +7,49 @@ const STATUS_CODE_PAGE_REGEXP = /\/[0-9]{3}\/?$/; /** Construct sitemap.xml given a set of URLs */ export function generateSitemap(pages: string[], finalSiteUrl: string, opts: SitemapOptions) { - const { changefreq, priority: prioritySrc, lastmod: lastmodSrc, i18n } = opts || {}; - // TODO: find way to respect URLs here - const urls = [...pages].filter((url) => !STATUS_CODE_PAGE_REGEXP.test(url)); - urls.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })); // sort alphabetically so sitemap is same each time + const { changefreq, priority: prioritySrc, lastmod: lastmodSrc, i18n } = opts || {}; + // TODO: find way to respect URLs here + const urls = [...pages].filter((url) => !STATUS_CODE_PAGE_REGEXP.test(url)); + urls.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })); // sort alphabetically so sitemap is same each time - const lastmod = lastmodSrc?.toISOString(); - const priority = typeof prioritySrc === 'number' ? prioritySrc : undefined; + const lastmod = lastmodSrc?.toISOString(); + const priority = typeof prioritySrc === 'number' ? prioritySrc : undefined; - const { locales, defaultLocale } = i18n || {}; - const localeCodes = Object.keys(locales || {}); + const { locales, defaultLocale } = i18n || {}; + const localeCodes = Object.keys(locales || {}); - const getPath = (url: string) => { - const result = parseUrl(url, i18n?.defaultLocale || '', localeCodes, finalSiteUrl); - return result?.path; - }; - const getLocale = (url: string) => { - const result = parseUrl(url, i18n?.defaultLocale || '', localeCodes, finalSiteUrl); - return result?.locale; - }; + const getPath = (url: string) => { + const result = parseUrl(url, i18n?.defaultLocale || '', localeCodes, finalSiteUrl); + return result?.path; + }; + const getLocale = (url: string) => { + const result = parseUrl(url, i18n?.defaultLocale || '', localeCodes, finalSiteUrl); + return result?.locale; + }; - const urlData = urls.map((url) => { - let links; - if (defaultLocale && locales) { - const currentPath = getPath(url); - if (currentPath) { - const filtered = urls.filter((subUrl) => getPath(subUrl) === currentPath); - if (filtered.length > 1) { - links = filtered.map((subUrl) => ({ - url: subUrl, - lang: locales[getLocale(subUrl)!], - })); - } - } - } + const urlData = urls.map((url) => { + let links; + if (defaultLocale && locales) { + const currentPath = getPath(url); + if (currentPath) { + const filtered = urls.filter((subUrl) => getPath(subUrl) === currentPath); + if (filtered.length > 1) { + links = filtered.map((subUrl) => ({ + url: subUrl, + lang: locales[getLocale(subUrl)!], + })); + } + } + } - return { - url, - links, - lastmod, - priority, - changefreq, // : changefreq as EnumChangefreq, - } as SitemapItemLoose; - }); + return { + url, + links, + lastmod, + priority, + changefreq, // : changefreq as EnumChangefreq, + } as SitemapItemLoose; + }); - return urlData; + return urlData; } diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index d0004ae91..700f68876 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -1,148 +1,138 @@ -import path from 'node:path'; import { fileURLToPath } from 'url'; import type { AstroConfig, AstroIntegration } from 'astro'; import { ZodError } from 'zod'; import { LinkItem as LinkItemBase, SitemapItemLoose, simpleSitemapAndIndex } from 'sitemap'; import { Logger } from './utils/logger'; -import { withOptions } from './with-options'; -import { validateOpts } from './validate-opts'; -import { generateSitemap } from './generate-sitemap'; import { changefreqValues } from './constants'; -import { processPages } from './process-pages'; +import { validateOptions } from './validate-options'; +import { generateSitemap } from './generate-sitemap'; export type ChangeFreq = typeof changefreqValues[number]; -export type SitemapItem = Pick; +export type SitemapItem = Pick< + SitemapItemLoose, + 'url' | 'lastmod' | 'changefreq' | 'priority' | 'links' +>; export type LinkItem = LinkItemBase; export type SitemapOptions = - | { - // the same with official - filter?(page: string): boolean; - customPages?: string[]; - canonicalURL?: string; - // added - i18n?: { - defaultLocale: string; - locales: Record; - }; - entryLimit?: number; + | { + filter?(page: string): boolean; + customPages?: string[]; + canonicalURL?: string; - createLinkInHead?: boolean; - serialize?(item: SitemapItemLoose): SitemapItemLoose; - // sitemap specific - changefreq?: ChangeFreq; - lastmod?: Date; - priority?: number; - } - | undefined; + i18n?: { + defaultLocale: string; + locales: Record; + }; + // number of entries per sitemap file + entryLimit?: number; + + // sitemap specific + changefreq?: ChangeFreq; + lastmod?: Date; + priority?: number; + + // called for each sitemap item just before to save them on disk, sync or async + serialize?(item: SitemapItemLoose): SitemapItemLoose; + } + | undefined; function formatConfigErrorMessage(err: ZodError) { - const errorList = err.issues.map((issue) => ` ${issue.path.join('.')} ${issue.message + '.'}`); - return errorList.join('\n'); + const errorList = err.issues.map((issue) => ` ${issue.path.join('.')} ${issue.message + '.'}`); + return errorList.join('\n'); } const PKG_NAME = '@astrojs/sitemap'; const OUTFILE = 'sitemap-index.xml'; const createPlugin = (options?: SitemapOptions): AstroIntegration => { - let config: AstroConfig; - return { - name: PKG_NAME, + let config: AstroConfig; + return { + name: PKG_NAME, - hooks: { - 'astro:config:done': async ({ config: cfg }) => { - config = cfg; - }, + hooks: { + 'astro:config:done': async ({ config: cfg }) => { + config = cfg; + }, - 'astro:build:done': async ({ dir, pages }) => { - const logger = new Logger(PKG_NAME); + 'astro:build:done': async ({ dir, pages }) => { + const logger = new Logger(PKG_NAME); - const opts = withOptions(options || {}); + try { + const opts = validateOptions(config.site, options); - try { - validateOpts(config.site, opts); + const { filter, customPages, canonicalURL, serialize, entryLimit } = opts; - const { filter, customPages, canonicalURL, serialize, createLinkInHead, entryLimit } = opts; + let finalSiteUrl: URL; + if (canonicalURL) { + finalSiteUrl = new URL(canonicalURL); + if (!finalSiteUrl.pathname.endsWith('/')) { + finalSiteUrl.pathname += '/'; // normalizes the final url since it's provided by user + } + } else { + // `validateOptions` forces to provide `canonicalURL` or `config.site` at least. + // So step to check on empty values of `canonicalURL` and `config.site` is dropped. + finalSiteUrl = new URL(config.base, config.site); + } - let finalSiteUrl: URL; - if (canonicalURL) { - finalSiteUrl = new URL(canonicalURL); - if (!finalSiteUrl.pathname.endsWith('/')) { - finalSiteUrl.pathname += '/'; // normalizes the final url since it's provided by user - } - } else { - // `validateOpts` forces to provide `canonicalURL` or `config.site` at least. - // So step to check on empty values of `canonicalURL` and `config.site` is dropped. - finalSiteUrl = new URL(config.base, config.site); - } + let pageUrls = pages.map((p) => { + const path = finalSiteUrl.pathname + p.pathname; + return new URL(path, finalSiteUrl).href; + }); - let pageUrls = pages.map((p) => { - const path = finalSiteUrl.pathname + p.pathname; - return new URL(path, finalSiteUrl).href; - }); + try { + if (filter) { + pageUrls = pageUrls.filter(filter); + } + } catch (err) { + logger.error(`Error filtering pages\n${(err as any).toString()}`); + return; + } - try { - if (filter) { - pageUrls = pageUrls.filter((url) => filter(url)); - } - } catch (err) { - logger.error(`Error filtering pages\n${(err as any).toString()}`); - return; - } + if (customPages) { + pageUrls = [...pageUrls, ...customPages]; + } - if (customPages) { - pageUrls = [...pageUrls, ...customPages]; - } + if (pageUrls.length === 0) { + logger.warn(`No data for sitemap.\n\`${OUTFILE}\` is not created.`); + return; + } - if (pageUrls.length === 0) { - logger.warn(`No data for sitemap.\n\`${OUTFILE}\` is not created.`); - return; - } + let urlData = generateSitemap(pageUrls, finalSiteUrl.href, opts); - let urlData = generateSitemap(pageUrls, finalSiteUrl.href, opts); + if (serialize) { + try { + const serializedUrls: SitemapItemLoose[] = []; + for (const item of urlData) { + const serialized = await Promise.resolve(serialize(item)); + serializedUrls.push(serialized); + } + urlData = serializedUrls; + } catch (err) { + logger.error(`Error serializing pages\n${(err as any).toString()}`); + return; + } + } - let serializedUrls: SitemapItemLoose[]; - - if (serialize) { - serializedUrls = []; - try { - for (const item of urlData) { - const serialized = await Promise.resolve(serialize(item)); - serializedUrls.push(serialized); - } - urlData = serializedUrls; - } catch (err) { - logger.error(`Error serializing pages\n${(err as any).toString()}`); - return; - } - } - - await simpleSitemapAndIndex({ - hostname: finalSiteUrl.href, - destinationDir: fileURLToPath(dir), - sourceData: urlData, - limit: entryLimit, - gzip: false, - }); - logger.success(`\`${OUTFILE}\` is created.`); - - if (createLinkInHead) { - const sitemapHref = path.posix.join(config.base, OUTFILE); - const headHTML = ``; - await processPages(pages, dir, headHTML, config.build.format); - logger.success('Sitemap links are created in section of generated pages.'); - } - } catch (err) { - if (err instanceof ZodError) { - logger.warn(formatConfigErrorMessage(err)); - } else { - throw err; - } - } - }, - }, - }; + await simpleSitemapAndIndex({ + hostname: finalSiteUrl.href, + destinationDir: fileURLToPath(dir), + sourceData: urlData, + limit: entryLimit, + gzip: false, + }); + logger.success(`\`${OUTFILE}\` is created.`); + } catch (err) { + if (err instanceof ZodError) { + logger.warn(formatConfigErrorMessage(err)); + } else { + throw err; + } + } + }, + }, + }; }; export default createPlugin; diff --git a/packages/integrations/sitemap/src/process-pages.ts b/packages/integrations/sitemap/src/process-pages.ts deleted file mode 100644 index c6a46218c..000000000 --- a/packages/integrations/sitemap/src/process-pages.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { promises as fs } from 'node:fs'; -import { parse, HTMLElement } from 'node-html-parser'; - -const addTailSlash = (s: string) => (s.endsWith('/') ? s : s + '/'); -const removeHeadingSlash = (s: string) => s.replace(/^\/+/, ''); -const removeTrailingSlash = (s: string) => s.replace(/\/+$/, ''); - -const getFileDir = (pathname: string) => { - const name = addTailSlash(pathname); - const file = name === '404/' ? '404.html' : `${name}index.html`; - return removeHeadingSlash(file); -}; - -const getFileFile = (pathname: string) => (pathname ? `${removeTrailingSlash(pathname)}.html` : 'index.html'); - -export async function processPages(pages: { pathname: string }[], dir: URL, headHTML: string, buildFormat: string) { - if (pages.length === 0) { - return; - } - if (buildFormat !== 'directory' && buildFormat !== 'file') { - throw new Error(`Unsupported build.format: '${buildFormat}' in your astro.config`); - } - - for (const page of pages) { - const fileUrl = new URL(buildFormat === 'directory' ? getFileDir(page.pathname) : getFileFile(page.pathname), dir); - - const html = await fs.readFile(fileUrl, 'utf-8'); - const root = parse(html); - let head = root.querySelector('head'); - if (!head) { - head = new HTMLElement('head', {}, '', root); - root.appendChild(head); - console.warn(`No found in \`${fileUrl.pathname}\`. will be created.`); - } - head.innerHTML = head.innerHTML + headHTML; - const inlined = root.toString(); - await fs.writeFile(fileUrl, inlined, 'utf-8'); - } -} diff --git a/packages/integrations/sitemap/src/schema.ts b/packages/integrations/sitemap/src/schema.ts index 6e58218c7..723f9ac58 100644 --- a/packages/integrations/sitemap/src/schema.ts +++ b/packages/integrations/sitemap/src/schema.ts @@ -1,52 +1,47 @@ import { z } from 'zod'; -import { isValidUrl } from './utils/is-valid-url'; import { changefreqValues } from './constants'; - -const urlSchema = () => - z - .string() - .min(1) - .refine((val) => !val || isValidUrl(val), 'Not valid url'); +import { SITEMAP_CONFIG_DEFAULTS } from './config-defaults'; const localeKeySchema = () => z.string().min(1); const isFunction = (fn: any) => fn instanceof Function; -const fnSchema = () => z - .any() - .refine((val) => !val || isFunction(val), { message: 'Not a function' }) - .optional(); +const fnSchema = () => + z + .any() + .refine((val) => !val || isFunction(val), { message: 'Not a function' }) + .optional(); -export const SitemapOptionsSchema = z.object({ - filter: fnSchema(), +export const SitemapOptionsSchema = z + .object({ + filter: fnSchema(), + customPages: z.string().url().array().optional(), + canonicalURL: z.string().url().optional(), - customPages: urlSchema().array().optional(), + i18n: z + .object({ + defaultLocale: localeKeySchema(), + locales: z.record( + localeKeySchema(), + z + .string() + .min(2) + .regex(/^[a-zA-Z\-]+$/gm, { + message: 'Only English alphabet symbols and hyphen allowed', + }) + ), + }) + .refine((val) => !val || val.locales[val.defaultLocale], { + message: '`defaultLocale` must exists in `locales` keys', + }) + .optional(), - canonicalURL: urlSchema().optional(), + entryLimit: z.number().nonnegative().default(SITEMAP_CONFIG_DEFAULTS.entryLimit), + serialize: fnSchema(), - i18n: z - .object({ - defaultLocale: localeKeySchema(), - locales: z.record( - localeKeySchema(), - z - .string() - .min(2) - .regex(/^[a-zA-Z\-]+$/gm, { message: 'Only English alphabet symbols and hyphen allowed' }), - ), - }) - .refine(({ locales, defaultLocale }) => locales[defaultLocale], { - message: '`defaultLocale` must exists in `locales` keys', - }) - .optional(), - - createLinkInHead: z.boolean().optional(), - - entryLimit: z.number().nonnegative().optional(), - - serialize: fnSchema(), - - changefreq: z.enum(changefreqValues).optional(), - lastmod: z.date().optional(), - priority: z.number().min(0).max(1).optional(), -}); + changefreq: z.enum(changefreqValues).optional(), + lastmod: z.date().optional(), + priority: z.number().min(0).max(1).optional(), + }) + .strict() + .default(SITEMAP_CONFIG_DEFAULTS); diff --git a/packages/integrations/sitemap/src/utils/is-object-empty.ts b/packages/integrations/sitemap/src/utils/is-object-empty.ts index 2dbc0cfe9..0d6181069 100644 --- a/packages/integrations/sitemap/src/utils/is-object-empty.ts +++ b/packages/integrations/sitemap/src/utils/is-object-empty.ts @@ -1,10 +1,10 @@ // @internal export const isObjectEmpty = (o: any) => { - if (!o) { - return true; - } - if (Array.isArray(o)) { - return o.length === 0; - } - return Object.keys(o).length === 0 && Object.getPrototypeOf(o) === Object.prototype; + if (!o) { + return true; + } + if (Array.isArray(o)) { + return o.length === 0; + } + return Object.keys(o).length === 0 && Object.getPrototypeOf(o) === Object.prototype; }; diff --git a/packages/integrations/sitemap/src/utils/is-valid-url.ts b/packages/integrations/sitemap/src/utils/is-valid-url.ts index 4bead70fd..b140623b0 100644 --- a/packages/integrations/sitemap/src/utils/is-valid-url.ts +++ b/packages/integrations/sitemap/src/utils/is-valid-url.ts @@ -1,13 +1,13 @@ // @internal export const isValidUrl = (s: any) => { - if (typeof s !== 'string' || !s) { - return false; - } - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const dummy = new URL(s); - return true; - } catch { - return false; - } + if (typeof s !== 'string' || !s) { + return false; + } + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const dummy = new URL(s); + return true; + } catch { + return false; + } }; diff --git a/packages/integrations/sitemap/src/utils/logger.ts b/packages/integrations/sitemap/src/utils/logger.ts index ff6276a2e..203baeaa7 100644 --- a/packages/integrations/sitemap/src/utils/logger.ts +++ b/packages/integrations/sitemap/src/utils/logger.ts @@ -1,46 +1,46 @@ // @internal export interface ILogger { - info(msg: string): void; - success(msg: string): void; - warn(msg: string): void; - error(msg: string): void; + info(msg: string): void; + success(msg: string): void; + warn(msg: string): void; + error(msg: string): void; } // @internal export class Logger implements ILogger { - private colors = { - reset: '\x1b[0m', - fg: { - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - }, - } as const; + private colors = { + reset: '\x1b[0m', + fg: { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + }, + } as const; - private packageName: string; + private packageName: string; - constructor(packageName: string) { - this.packageName = packageName; - } + constructor(packageName: string) { + this.packageName = packageName; + } - private log(msg: string, prefix: string = '') { - // eslint-disable-next-line no-console - console.log(`%s${this.packageName}:%s ${msg}\n`, prefix, prefix ? this.colors.reset : ''); - } + private log(msg: string, prefix: string = '') { + // eslint-disable-next-line no-console + console.log(`%s${this.packageName}:%s ${msg}\n`, prefix, prefix ? this.colors.reset : ''); + } - info(msg: string) { - this.log(msg); - } + info(msg: string) { + this.log(msg); + } - success(msg: string) { - this.log(msg, this.colors.fg.green); - } + success(msg: string) { + this.log(msg, this.colors.fg.green); + } - warn(msg: string) { - this.log(`Skipped!\n${msg}`, this.colors.fg.yellow); - } + warn(msg: string) { + this.log(`Skipped!\n${msg}`, this.colors.fg.yellow); + } - error(msg: string) { - this.log(`Failed!\n${msg}`, this.colors.fg.red); - } + error(msg: string) { + this.log(`Failed!\n${msg}`, this.colors.fg.red); + } } diff --git a/packages/integrations/sitemap/src/utils/parse-url.ts b/packages/integrations/sitemap/src/utils/parse-url.ts index 7d948923a..f9189cf7d 100644 --- a/packages/integrations/sitemap/src/utils/parse-url.ts +++ b/packages/integrations/sitemap/src/utils/parse-url.ts @@ -1,28 +1,39 @@ -export const parseUrl = (url: string, defaultLocale: string, localeCodes: string[], base: string) => { - if (!url || !defaultLocale || localeCodes.length === 0 || localeCodes.some((key) => !key) || !base) { - throw new Error('parseUrl: some parameters are empty'); - } - if (url.indexOf(base) !== 0) { - return undefined; - } - let s = url.replace(base, ''); - if (!s || s === '/') { - return { locale: defaultLocale, path: '/' }; - } - if (!s.startsWith('/')) { - s = '/' + s; - } - const a = s.split('/'); - const locale = a[1]; - if (localeCodes.some((key) => key === locale)) { - let path = a.slice(2).join('/'); - if (path === '//') { - path = '/'; - } - if (path !== '/' && !path.startsWith('/')) { - path = '/' + path; - } - return { locale, path }; - } - return { locale: defaultLocale, path: s }; +export const parseUrl = ( + url: string, + defaultLocale: string, + localeCodes: string[], + base: string +) => { + if ( + !url || + !defaultLocale || + localeCodes.length === 0 || + localeCodes.some((key) => !key) || + !base + ) { + throw new Error('parseUrl: some parameters are empty'); + } + if (url.indexOf(base) !== 0) { + return undefined; + } + let s = url.replace(base, ''); + if (!s || s === '/') { + return { locale: defaultLocale, path: '/' }; + } + if (!s.startsWith('/')) { + s = '/' + s; + } + const a = s.split('/'); + const locale = a[1]; + if (localeCodes.some((key) => key === locale)) { + let path = a.slice(2).join('/'); + if (path === '//') { + path = '/'; + } + if (path !== '/' && !path.startsWith('/')) { + path = '/' + path; + } + return { locale, path }; + } + return { locale: defaultLocale, path: s }; }; diff --git a/packages/integrations/sitemap/src/validate-options.ts b/packages/integrations/sitemap/src/validate-options.ts new file mode 100644 index 000000000..f89582d82 --- /dev/null +++ b/packages/integrations/sitemap/src/validate-options.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; +import type { SitemapOptions } from './index'; +import { SitemapOptionsSchema } from './schema'; + +// @internal +export const validateOptions = (site: string | undefined, opts: SitemapOptions) => { + const result = SitemapOptionsSchema.parse(opts); + + z.object({ + site: z.string().optional(), // Astro takes care of `site`: how to validate, transform and refine + canonicalURL: z.string().optional(), // `canonicalURL` is already validated in prev step + }) + .refine(({ site, canonicalURL }) => site || canonicalURL, { + message: 'Required `site` astro.config option or `canonicalURL` integration option', + }) + .parse({ + site, + canonicalURL: result.canonicalURL, + }); + + return result; +}; diff --git a/packages/integrations/sitemap/src/validate-opts.ts b/packages/integrations/sitemap/src/validate-opts.ts deleted file mode 100644 index 2a9bfe667..000000000 --- a/packages/integrations/sitemap/src/validate-opts.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from 'zod'; -import type { SitemapOptions } from './index'; -import { SitemapOptionsSchema } from './schema'; - -// @internal -export const validateOpts = (site: string | undefined, opts: SitemapOptions) => { - const schema = SitemapOptionsSchema.extend({ - site: z.string().optional(), - }) - .strict() - .refine(({ site, canonicalURL }) => site || canonicalURL, { - message: 'Required `site` astro.config option or `canonicalURL` integration option', - }); - - schema.parse({ site: site || '', ...(opts || {}) }); -}; diff --git a/packages/integrations/sitemap/src/with-options.ts b/packages/integrations/sitemap/src/with-options.ts deleted file mode 100644 index 6e0982b7b..000000000 --- a/packages/integrations/sitemap/src/with-options.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isObjectEmpty } from './utils/is-object-empty'; -import type { SitemapOptions } from './index'; - -const defaultOptions: Readonly = { - createLinkInHead: true, - entryLimit: 45000, -}; - -// @internal -export const withOptions = (pluginOptions: SitemapOptions) => { - if (isObjectEmpty(pluginOptions)) { - return defaultOptions; - } - const options: SitemapOptions = { - ...pluginOptions, - createLinkInHead: pluginOptions?.createLinkInHead ?? defaultOptions.createLinkInHead, - entryLimit: pluginOptions?.entryLimit || defaultOptions.entryLimit, - }; - return options; -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5c4fd6c8..d8538418d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1800,11 +1800,9 @@ importers: specifiers: astro: workspace:* astro-scripts: workspace:* - node-html-parser: ^5.3.3 sitemap: ^7.1.1 zod: ^3.17.3 dependencies: - node-html-parser: 5.3.3 sitemap: 7.1.1 zod: 3.17.3 devDependencies: @@ -8075,16 +8073,6 @@ packages: engines: {node: '>=8'} dev: true - /css-select/4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - dev: false - /css-select/5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} dependencies: @@ -8101,6 +8089,7 @@ packages: /css-what/6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + dev: true /cssesc/3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -9612,6 +9601,7 @@ packages: /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + dev: true /hosted-git-info/2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -11123,13 +11113,6 @@ packages: hasBin: true dev: false - /node-html-parser/5.3.3: - resolution: {integrity: sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==} - dependencies: - css-select: 4.3.0 - he: 1.2.0 - dev: false - /node-pre-gyp/0.13.0: resolution: {integrity: sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==} deprecated: 'Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future'