@mintlify/common 1.0.1160 → 1.0.1161

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/dist/index.d.ts CHANGED
@@ -42,3 +42,4 @@ export * from './knownMcpClients.js';
42
42
  export * from './live-preview.js';
43
43
  export * from './paths/isEqualIgnoringLeadingSlash.js';
44
44
  export * from './sdk/index.js';
45
+ export * from './seo/resolveSeoMetatags.js';
package/dist/index.js CHANGED
@@ -42,3 +42,4 @@ export * from './knownMcpClients.js';
42
42
  export * from './live-preview.js';
43
43
  export * from './paths/isEqualIgnoringLeadingSlash.js';
44
44
  export * from './sdk/index.js';
45
+ export * from './seo/resolveSeoMetatags.js';
@@ -0,0 +1,12 @@
1
+ export type SeoMetatagsConfig = {
2
+ metatags?: Record<string, string>;
3
+ paths?: {
4
+ path: string;
5
+ metatags: Record<string, string>;
6
+ }[];
7
+ };
8
+ /**
9
+ * Merges `seo.metatags` with every `seo.paths` entry whose folder contains the page.
10
+ * Shorter paths apply first, so the most specific folder wins.
11
+ */
12
+ export declare function resolveSeoMetatags(seo: SeoMetatagsConfig | undefined, href: string | undefined): Record<string, string>;
@@ -0,0 +1,18 @@
1
+ function toPagePath(href) {
2
+ return href.split(/[?#]/)[0].replace(/^\/+/, '').replace(/\/+$/, '');
3
+ }
4
+ /**
5
+ * Merges `seo.metatags` with every `seo.paths` entry whose folder contains the page.
6
+ * Shorter paths apply first, so the most specific folder wins.
7
+ */
8
+ export function resolveSeoMetatags(seo, href) {
9
+ var _a, _b;
10
+ const siteMetatags = (_a = seo === null || seo === void 0 ? void 0 : seo.metatags) !== null && _a !== void 0 ? _a : {};
11
+ if (!((_b = seo === null || seo === void 0 ? void 0 : seo.paths) === null || _b === void 0 ? void 0 : _b.length) || !href)
12
+ return Object.assign({}, siteMetatags);
13
+ const pagePath = toPagePath(href);
14
+ const matches = seo.paths
15
+ .filter(({ path }) => pagePath === path || pagePath.startsWith(`${path}/`))
16
+ .sort((a, b) => a.path.length - b.path.length);
17
+ return Object.assign({}, siteMetatags, ...matches.map(({ metatags }) => metatags));
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveSeoMetatags } from './resolveSeoMetatags.js';
3
+ const seo = {
4
+ metatags: { tenant: 'Acme', product: 'Docs' },
5
+ paths: [
6
+ { path: 'guides/v2', metatags: { 'doc-version': '2', latest: 'true' } },
7
+ { path: 'guides', metatags: { latest: 'false', should_index: 'true' } },
8
+ { path: 'sdk', metatags: { product: 'SDK' } },
9
+ ],
10
+ };
11
+ describe('resolveSeoMetatags', () => {
12
+ it('returns site metatags when no path matches', () => {
13
+ expect(resolveSeoMetatags(seo, '/changelog')).toEqual({ tenant: 'Acme', product: 'Docs' });
14
+ });
15
+ it('returns an empty object without seo config', () => {
16
+ expect(resolveSeoMetatags(undefined, '/guides/intro')).toEqual({});
17
+ });
18
+ it('returns site metatags without an href', () => {
19
+ expect(resolveSeoMetatags(seo, undefined)).toEqual({ tenant: 'Acme', product: 'Docs' });
20
+ });
21
+ it('applies a matching folder on top of site metatags', () => {
22
+ expect(resolveSeoMetatags(seo, '/guides/intro')).toEqual({
23
+ tenant: 'Acme',
24
+ product: 'Docs',
25
+ latest: 'false',
26
+ should_index: 'true',
27
+ });
28
+ });
29
+ it('lets the longest path win regardless of declaration order', () => {
30
+ expect(resolveSeoMetatags(seo, '/guides/v2/install')).toEqual({
31
+ tenant: 'Acme',
32
+ product: 'Docs',
33
+ 'doc-version': '2',
34
+ latest: 'true',
35
+ should_index: 'true',
36
+ });
37
+ });
38
+ it('lets a folder override a site metatag', () => {
39
+ expect(resolveSeoMetatags(seo, 'sdk/node')).toEqual({ tenant: 'Acme', product: 'SDK' });
40
+ });
41
+ it('only matches on whole path segments', () => {
42
+ expect(resolveSeoMetatags(seo, '/guides/v20/install')).toEqual({
43
+ tenant: 'Acme',
44
+ product: 'Docs',
45
+ latest: 'false',
46
+ should_index: 'true',
47
+ });
48
+ expect(resolveSeoMetatags(seo, '/sdk-legacy/node')).toEqual({
49
+ tenant: 'Acme',
50
+ product: 'Docs',
51
+ });
52
+ });
53
+ it('matches a page whose href equals the path', () => {
54
+ expect(resolveSeoMetatags(seo, '/sdk')).toEqual({ tenant: 'Acme', product: 'SDK' });
55
+ });
56
+ it.each([
57
+ ['a query string', '/sdk?ref=nav'],
58
+ ['a fragment', '/sdk#overview'],
59
+ ['a query string and fragment', '/sdk?ref=nav#overview'],
60
+ ['a trailing slash', '/sdk/'],
61
+ ])('matches a folder href with %s', (_label, href) => {
62
+ expect(resolveSeoMetatags(seo, href)).toEqual({ tenant: 'Acme', product: 'SDK' });
63
+ });
64
+ it('matches a nested page whose href carries a query string or fragment', () => {
65
+ expect(resolveSeoMetatags(seo, '/sdk/node?ref=nav#install')).toEqual({
66
+ tenant: 'Acme',
67
+ product: 'SDK',
68
+ });
69
+ });
70
+ it('does not match a different folder that shares a prefix', () => {
71
+ expect(resolveSeoMetatags(seo, '/sdk-legacy?ref=nav')).toEqual({
72
+ tenant: 'Acme',
73
+ product: 'Docs',
74
+ });
75
+ });
76
+ it('does not mutate the config', () => {
77
+ const before = JSON.stringify(seo);
78
+ resolveSeoMetatags(seo, '/guides/v2/install');
79
+ expect(JSON.stringify(seo)).toBe(before);
80
+ });
81
+ });