81dce94f2a
* chore: strictNullChecks for zod * feat: expose `rssSchema` helper * refactor: align types with schema types * feat: break glob handler to globToRssItems util * refactor: RSS options validation to Zod * refactor: avoid intermediate type * fix: allow numbers and dates in pubDate * test: update glob and error tests * feat: add rss to with-content starter * fix: move globToRssItems back to internal behavior * chore: JSON.stringify * Revert "fix: move globToRssItems back to internal behavior" This reverts commit 85305075e6444907455541b24bccbccd5016951a. * test: missing url * docs: `import.meta.env.SITE` -> `context.site` * docs: update README to content collections example * fix: url -> link * docs: add `rssSchema` to README * chore: consistent formatting * docs: add `pagesGlobToRssItems()` reference * chore: globToRssItems -> pagesGlobToRssItems * chore: changeset * fix: bad docs line highlighting * fix: add collections export to example * nit: remove "our" * fix: are -> all * fix: more README edits * deps: kleur * chore: add back import.meta.glob handling as deprecated * docs: bump down to `minor`, update headline to be less content collections-y * typo: suggest adding * chore: support URL object on `site` * docs: add await to pagesGlob ex * docs: tighten `rssSchema` explainer * docs: tighten pagesGlobToRssItems section * docs: add content to README * docs: replace examples with docs link * docs: re-we Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
import { z } from 'astro/zod';
|
||
|
||
/** Normalize URL to its canonical form */
|
||
export function createCanonicalURL(url: string, base?: string): URL {
|
||
let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical
|
||
pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections)
|
||
if (!getUrlExtension(url)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension
|
||
pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() won’t)
|
||
return new URL(pathname, base);
|
||
}
|
||
|
||
/** Check if a URL is already valid */
|
||
export function isValidURL(url: string): boolean {
|
||
try {
|
||
new URL(url);
|
||
return true;
|
||
} catch (e) {}
|
||
return false;
|
||
}
|
||
|
||
function getUrlExtension(url: string) {
|
||
const lastDot = url.lastIndexOf('.');
|
||
const lastSlash = url.lastIndexOf('/');
|
||
return lastDot > lastSlash ? url.slice(lastDot + 1) : '';
|
||
}
|
||
|
||
const flattenErrorPath = (errorPath: (string | number)[]) => errorPath.join('.');
|
||
|
||
export const errorMap: z.ZodErrorMap = (error, ctx) => {
|
||
if (error.code === 'invalid_type') {
|
||
const badKeyPath = JSON.stringify(flattenErrorPath(error.path));
|
||
if (error.received === 'undefined') {
|
||
return { message: `${badKeyPath} is required.` };
|
||
} else {
|
||
return { message: `${badKeyPath} should be ${error.expected}, not ${error.received}.` };
|
||
}
|
||
}
|
||
return { message: ctx.defaultError };
|
||
};
|