@thenewdynamic/astro-seo 0.1.0 → 0.1.3

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/README.md CHANGED
@@ -23,6 +23,7 @@ npm install @thenewdynamic/astro-seo
23
23
  import tndSeo from '@thenewdynamic/astro-seo'
24
24
 
25
25
  export default defineConfig({
26
+ site: 'https://example.com',
26
27
  integrations: [tndSeo()]
27
28
  })
28
29
  ```
@@ -32,7 +33,17 @@ export default defineConfig({
32
33
  import type { SeoUserConfig } from '@thenewdynamic/astro-seo'
33
34
 
34
35
  export default {
35
- site: new URL('https://example.com'),
36
+ defaults: {
37
+ image: '/og-image.png', // Required — default OG image
38
+ title: 'My Site',
39
+ description: 'A brief site description',
40
+ },
41
+ // Optional — customize image resolution (e.g., Sanity)
42
+ resolveImage: (image) => `https://cdn.example.com/${image.src}`,
43
+ // Optional — override SEO fields per content type
44
+ transformEntry: (entry) => ({ /* ... */ }),
45
+ // Optional — detect production mode (defaults to import.meta.env.PROD)
46
+ isProd: () => process.env.NODE_ENV === 'production',
36
47
  } satisfies SeoUserConfig
37
48
  ```
38
49
 
@@ -1,3 +1,3 @@
1
1
  import type { SeoUserConfig, SeoEntry, SeoData } from '../types.js';
2
- export declare const makeGetData: (config: SeoUserConfig) => (entry: SeoEntry) => SeoData;
2
+ export declare const makeGetData: (config?: SeoUserConfig) => (entry: SeoEntry) => SeoData;
3
3
  //# sourceMappingURL=getData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getData.d.ts","sourceRoot":"","sources":["../../src/core/getData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAGnE,eAAO,MAAM,WAAW,GAAI,QAAQ,aAAa,MAAM,OAAO,QAAQ,KAAG,OAkIxE,CAAA"}
1
+ {"version":3,"file":"getData.d.ts","sourceRoot":"","sources":["../../src/core/getData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAGnE,eAAO,MAAM,WAAW,GAAI,SAAQ,aAAkB,MAAM,OAAO,QAAQ,KAAG,OAoI7E,CAAA"}
@@ -1,21 +1,24 @@
1
1
  import { escapeString, makeAbsUrl, isHome, getExcerpt } from '../utils.js';
2
- export const makeGetData = (config) => (entry) => {
3
- const { site, resolveImage, transformEntry } = config;
4
- const { url: baseURL } = site;
5
- const absUrl = makeAbsUrl(baseURL);
6
- const { title: siteTitle, description: siteDescription, image: siteImage, seo: { twitterHandle: siteTwitterHandle } = {}, prod, } = site;
7
- let { title = 'Missing', type = 'website', _type, _updatedAt, time_start, time_end, venue, date, url, description, descriptionText, locale = 'en_US', image, authors = [], bodyText, translation, twitterCard = 'summary_large_image', twitterHandle = siteTwitterHandle, twitterCreatorHandle = siteTwitterHandle, } = entry;
2
+ export const makeGetData = (config = {}) => (entry) => {
3
+ const { resolveImage, transformEntry, isProd } = config;
4
+ const urlInput = config.defaults?.url;
5
+ const baseURL = typeof urlInput === 'string' ? urlInput : urlInput?.toString?.();
6
+ const absUrl = baseURL ? makeAbsUrl(baseURL) : undefined;
7
+ const { title: defaultsTitle, description: defaultsDescription, image: defaultsImage, seo: { twitterHandle: defaultsTwitterHandle } = {}, } = config.defaults ?? {};
8
+ let { title = 'Website', type = 'website', _type, _updatedAt, time_start, time_end, venue, date, url, description, descriptionText, locale = 'en_US', image, authors = [], bodyText, translation, twitterCard = 'summary_large_image', twitterHandle = defaultsTwitterHandle, twitterCreatorHandle = defaultsTwitterHandle, } = entry;
8
9
  const seo = entry.seo || {};
9
10
  const { title: seoTitle, description: seoDescription, image: seoImage, canonical: seoCanonical, private: seoPrivate = false, } = seo;
10
11
  type = _type === 'post' ? 'article' : 'website';
11
- url = url ? absUrl(url) || undefined : undefined;
12
- const isPrivate = seoPrivate || !(prod?.() ?? true);
12
+ url = url && absUrl ? absUrl(url) || undefined : undefined;
13
+ const isPrivate = seoPrivate || !(isProd?.() ?? true);
13
14
  const canonical = seoCanonical || url;
15
+ // Title: seo > entry > defaults > "Website"
14
16
  title = seoTitle
15
17
  ? seoTitle
16
- : title
18
+ : title !== 'Website'
17
19
  ? escapeString(title)
18
- : siteTitle ?? 'Missing';
20
+ : defaultsTitle || 'Website';
21
+ // Description: seo > entry text > defaults > ""
19
22
  description = seoDescription
20
23
  ? seoDescription
21
24
  : descriptionText
@@ -24,17 +27,17 @@ export const makeGetData = (config) => (entry) => {
24
27
  ? escapeString(description)
25
28
  : bodyText
26
29
  ? getExcerpt(bodyText, 300)
27
- : siteDescription;
30
+ : defaultsDescription || '';
28
31
  let ogTitle = title;
29
- if (siteTitle && !isHome(entry)) {
30
- title = `${title} | ${siteTitle}`;
32
+ if (defaultsTitle && !isHome(entry)) {
33
+ title = `${title} | ${defaultsTitle}`;
31
34
  }
32
35
  else if (isHome(entry)) {
33
- title = site.title;
34
- ogTitle = site.title;
36
+ title = defaultsTitle || title;
37
+ ogTitle = title;
35
38
  }
36
- const resolvedSiteImage = siteImage;
37
- image = seoImage || image || resolvedSiteImage;
39
+ const resolvedDefaultsImage = defaultsImage;
40
+ image = seoImage || image || resolvedDefaultsImage;
38
41
  let imageAlt = '';
39
42
  if (image && typeof image !== 'string') {
40
43
  const img = image;
@@ -43,7 +46,7 @@ export const makeGetData = (config) => (entry) => {
43
46
  ? resolveImage(img, { width: 1000 })
44
47
  : (img.src ?? img.url ?? '');
45
48
  }
46
- else if (image && typeof image === 'string') {
49
+ else if (image && typeof image === 'string' && baseURL) {
47
50
  image = baseURL + image;
48
51
  }
49
52
  const languageAlternates = translation
@@ -56,7 +59,7 @@ export const makeGetData = (config) => (entry) => {
56
59
  publishedTime: date,
57
60
  modifiedTime: _updatedAt,
58
61
  authors: authors?.length
59
- ? authors.map((a) => ({ name: a.title ?? a.name ?? '', url: a.url ? absUrl(a.url) : false }))
62
+ ? authors.map((a) => ({ name: a.title ?? a.name ?? '', url: a.url && absUrl ? absUrl(a.url) : false }))
60
63
  : [],
61
64
  description,
62
65
  canonical,
@@ -71,7 +74,7 @@ export const makeGetData = (config) => (entry) => {
71
74
  locale: locale,
72
75
  localeAlternate,
73
76
  languageAlternates,
74
- siteTitle: siteTitle ?? '',
77
+ siteTitle: defaultsTitle || '',
75
78
  twitterCard: twitterCard,
76
79
  twitterHandle: twitterHandle,
77
80
  twitterCreatorHandle: twitterCreatorHandle,
@@ -1 +1 @@
1
- {"version":3,"file":"getData.js","sourceRoot":"","sources":["../../src/core/getData.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"getData.js","sourceRoot":"","sources":["../../src/core/getData.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAK1D,eAAO,MAAM,cAAc,GAAI,QAAQ,aAAa,KAAG,QAKtD,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAK1D,eAAO,MAAM,cAAc,GAAI,QAAQ,aAAa,KAAG,QAUtD,CAAA"}
@@ -2,6 +2,9 @@ import { makeGetData } from './getData.js';
2
2
  import { makeGetMetasData } from './getMetasData.js';
3
3
  import { makeGetStructuredData } from './getStructuredData.js';
4
4
  export const createSeoUtils = (config) => {
5
+ if (!config?.defaults?.image) {
6
+ throw new Error('@thenewdynamic/astro-seo: `defaults.image` is required. Set a default OG image in your `seo.config.ts` (e.g., `image: "/og-image.png"` or a Sanity image object).');
7
+ }
5
8
  const getData = makeGetData(config);
6
9
  const getMetasData = makeGetMetasData(getData);
7
10
  const getStructuredData = makeGetStructuredData(getData);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAE9D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAqB,EAAY,EAAE;IAChE,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAC9C,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;IACxD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAA;AACrD,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAE9D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAqB,EAAY,EAAE;IAChE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,mKAAmK,CACpK,CAAA;IACH,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAC9C,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;IACxD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAA;AACrD,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAc/C,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,gBAAgB,CAmC5E;AAED,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAc/C,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,gBAAgB,CA2C5E;AAED,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -13,10 +13,12 @@ declare module 'virtual:tnd/seo' {
13
13
  export default function tndSeo(options = {}) {
14
14
  let configFilePath;
15
15
  let coreModulePath;
16
+ let astroSite;
16
17
  return {
17
18
  name: '@thenewdynamic/astro-seo',
18
19
  hooks: {
19
20
  'astro:config:setup': ({ config, updateConfig }) => {
21
+ astroSite = config.site?.toString();
20
22
  const root = fileURLToPath(config.root);
21
23
  const userConfigPath = options.configPath ?? './seo.config';
22
24
  configFilePath = resolve(root, userConfigPath);
@@ -33,7 +35,13 @@ export default function tndSeo(options = {}) {
33
35
  return [
34
36
  `import userConfig from '${configFilePath}'`,
35
37
  `import { createSeoUtils } from '${coreModulePath}'`,
36
- `export const { getData, getMetasData, getStructuredData } = createSeoUtils(userConfig)`,
38
+ `const astroSite = ${astroSite ? `'${astroSite}'` : 'undefined'}`,
39
+ `const defaultIsProd = () => import.meta.env.PROD`,
40
+ `const config = userConfig || {}`,
41
+ `config.defaults = config.defaults || {}`,
42
+ `if (astroSite && !config.defaults.url) config.defaults.url = astroSite`,
43
+ `if (!config.isProd) config.isProd = defaultIsProd`,
44
+ `export const { getData, getMetasData, getStructuredData } = createSeoUtils(config)`,
37
45
  ].join('\n');
38
46
  },
39
47
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAKnC,MAAM,iBAAiB,GAAG,iBAAiB,CAAA;AAC3C,MAAM,WAAW,GAAG,IAAI,GAAG,iBAAiB,CAAA;AAE5C,MAAM,oBAAoB,GAAG;;;;;;;CAO5B,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,UAAyB,EAAE;IACxD,IAAI,cAAsB,CAAA;IAC1B,IAAI,cAAsB,CAAA;IAE1B,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;gBACjD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACvC,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,IAAI,cAAc,CAAA;gBAC3D,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;gBAC9C,cAAc,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;gBAE3E,MAAM,MAAM,GAAW;oBACrB,IAAI,EAAE,qBAAqB;oBAC3B,SAAS,CAAC,EAAE;wBACV,IAAI,EAAE,KAAK,iBAAiB;4BAAE,OAAO,WAAW,CAAA;oBAClD,CAAC;oBACD,IAAI,CAAC,EAAE;wBACL,IAAI,EAAE,KAAK,WAAW;4BAAE,OAAM;wBAC9B,OAAO;4BACL,2BAA2B,cAAc,GAAG;4BAC5C,mCAAmC,cAAc,GAAG;4BACpD,wFAAwF;yBACzF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACd,CAAC;iBACF,CAAA;gBAED,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;YAC/C,CAAC;YACD,mBAAmB,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE;gBACvC,WAAW,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAA;YAClF,CAAC;SACF;KACF,CAAA;AACH,CAAC;AAGD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAKnC,MAAM,iBAAiB,GAAG,iBAAiB,CAAA;AAC3C,MAAM,WAAW,GAAG,IAAI,GAAG,iBAAiB,CAAA;AAE5C,MAAM,oBAAoB,GAAG;;;;;;;CAO5B,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,UAAyB,EAAE;IACxD,IAAI,cAAsB,CAAA;IAC1B,IAAI,cAAsB,CAAA;IAC1B,IAAI,SAA6B,CAAA;IAEjC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;gBACjD,SAAS,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAA;gBACnC,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACvC,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,IAAI,cAAc,CAAA;gBAC3D,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;gBAC9C,cAAc,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;gBAE3E,MAAM,MAAM,GAAW;oBACrB,IAAI,EAAE,qBAAqB;oBAC3B,SAAS,CAAC,EAAE;wBACV,IAAI,EAAE,KAAK,iBAAiB;4BAAE,OAAO,WAAW,CAAA;oBAClD,CAAC;oBACD,IAAI,CAAC,EAAE;wBACL,IAAI,EAAE,KAAK,WAAW;4BAAE,OAAM;wBAC9B,OAAO;4BACL,2BAA2B,cAAc,GAAG;4BAC5C,mCAAmC,cAAc,GAAG;4BACpD,qBAAqB,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE;4BACjE,kDAAkD;4BAClD,iCAAiC;4BACjC,yCAAyC;4BACzC,wEAAwE;4BACxE,mDAAmD;4BACnD,oFAAoF;yBACrF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACd,CAAC;iBACF,CAAA;gBAED,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;YAC/C,CAAC;YACD,mBAAmB,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE;gBACvC,WAAW,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAA;YAClF,CAAC;SACF;KACF,CAAA;AACH,CAAC;AAGD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
package/dist/types.d.ts CHANGED
@@ -1,13 +1,12 @@
1
- export interface SiteConfig {
2
- url: string;
3
- title: string;
1
+ export interface DefaultsConfig {
2
+ url?: string;
3
+ image: string | Record<string, unknown>;
4
+ title?: string;
4
5
  description?: string;
5
- image?: string | Record<string, unknown>;
6
6
  seo?: {
7
7
  title?: string;
8
8
  twitterHandle?: string;
9
9
  };
10
- prod?: () => boolean;
11
10
  }
12
11
  export interface ImageOptions {
13
12
  width?: number;
@@ -85,9 +84,10 @@ export interface SeoData {
85
84
  timeEnd?: string;
86
85
  }
87
86
  export interface SeoUserConfig {
88
- site: SiteConfig;
87
+ defaults?: DefaultsConfig;
89
88
  resolveImage?: (image: Record<string, unknown>, opts?: ImageOptions) => string;
90
89
  transformEntry?: (entry: SeoEntry) => Partial<SeoData>;
90
+ isProd?: () => boolean;
91
91
  }
92
92
  export interface SeoUtils {
93
93
  getData: (entry: SeoEntry) => SeoData;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,IAAI,CAAC,EAAE,MAAM,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAAA;IAChC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;IACD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAC,CAAA;IACrD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,kBAAkB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAA;IAChB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,MAAM,CAAA;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAA;IACrC,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC1D,iBAAiB,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thenewdynamic/astro-seo",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Astro integration for SEO meta tags and structured data",
6
6
  "author": "The New Dynamic",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "files": [
25
25
  "dist",
26
- "src/components"
26
+ "src"
27
27
  ],
28
28
  "publishConfig": {
29
29
  "access": "public"
@@ -41,11 +41,13 @@
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc",
44
+ "dev": "tsc --watch",
44
45
  "prepublishOnly": "npm run build"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@types/node": "^20.0.0",
48
49
  "astro": "^4.14.0",
50
+ "esbuild": "^0.28.0",
49
51
  "typescript": "^5.0.0"
50
52
  },
51
53
  "peerDependencies": {
@@ -0,0 +1,136 @@
1
+ import type { SeoUserConfig, SeoEntry, SeoData } from '../types.js'
2
+ import { escapeString, makeAbsUrl, isHome, getExcerpt } from '../utils.js'
3
+
4
+ export const makeGetData = (config: SeoUserConfig = {}) => (entry: SeoEntry): SeoData => {
5
+ const { resolveImage, transformEntry, isProd } = config
6
+ const urlInput = config.defaults?.url
7
+ const baseURL = typeof urlInput === 'string' ? urlInput : (urlInput as any)?.toString?.()
8
+ const absUrl = baseURL ? makeAbsUrl(baseURL) : undefined
9
+
10
+ const {
11
+ title: defaultsTitle,
12
+ description: defaultsDescription,
13
+ image: defaultsImage,
14
+ seo: { twitterHandle: defaultsTwitterHandle } = {},
15
+ } = config.defaults ?? {}
16
+
17
+ let {
18
+ title = 'Website',
19
+ type = 'website',
20
+ _type,
21
+ _updatedAt,
22
+ time_start,
23
+ time_end,
24
+ venue,
25
+ date,
26
+ url,
27
+ description,
28
+ descriptionText,
29
+ locale = 'en_US',
30
+ image,
31
+ authors = [],
32
+ bodyText,
33
+ translation,
34
+ twitterCard = 'summary_large_image',
35
+ twitterHandle = defaultsTwitterHandle,
36
+ twitterCreatorHandle = defaultsTwitterHandle,
37
+ } = entry
38
+
39
+ const seo = entry.seo || {}
40
+ const {
41
+ title: seoTitle,
42
+ description: seoDescription,
43
+ image: seoImage,
44
+ canonical: seoCanonical,
45
+ private: seoPrivate = false,
46
+ } = seo
47
+
48
+ type = _type === 'post' ? 'article' : 'website'
49
+ url = url && absUrl ? absUrl(url) || undefined : undefined
50
+
51
+ const isPrivate = seoPrivate || !(isProd?.() ?? true)
52
+ const canonical = seoCanonical || url
53
+
54
+ // Title: seo > entry > defaults > "Website"
55
+ title = seoTitle
56
+ ? seoTitle
57
+ : title !== 'Website'
58
+ ? escapeString(title as string)
59
+ : defaultsTitle || 'Website'
60
+
61
+ // Description: seo > entry text > defaults > ""
62
+ description = seoDescription
63
+ ? seoDescription
64
+ : descriptionText
65
+ ? escapeString(descriptionText)
66
+ : description && typeof description === 'string'
67
+ ? escapeString(description)
68
+ : bodyText
69
+ ? getExcerpt(bodyText, 300)
70
+ : defaultsDescription || ''
71
+
72
+ let ogTitle = title
73
+
74
+ if (defaultsTitle && !isHome(entry)) {
75
+ title = `${title} | ${defaultsTitle}`
76
+ } else if (isHome(entry)) {
77
+ title = defaultsTitle || title
78
+ ogTitle = title
79
+ }
80
+
81
+ const resolvedDefaultsImage = defaultsImage
82
+ image = seoImage || image || resolvedDefaultsImage
83
+
84
+ let imageAlt = ''
85
+
86
+ if (image && typeof image !== 'string') {
87
+ const img = image as Record<string, unknown>
88
+ imageAlt = img.altText as string ?? ''
89
+ image = resolveImage
90
+ ? resolveImage(img, { width: 1000 })
91
+ : (img.src ?? img.url ?? '') as string
92
+ } else if (image && typeof image === 'string' && baseURL) {
93
+ image = baseURL + image
94
+ }
95
+
96
+ const languageAlternates = translation
97
+ ? [{ href: translation.url, hrefLang: translation.lang }]
98
+ : undefined
99
+ const localeAlternate = translation ? [translation.lang] : undefined
100
+
101
+ let output: SeoData = {
102
+ _type,
103
+ title,
104
+ publishedTime: date,
105
+ modifiedTime: _updatedAt,
106
+ authors: authors?.length
107
+ ? authors.map((a) => ({ name: a.title ?? a.name ?? '', url: a.url && absUrl ? absUrl(a.url) : false }))
108
+ : [],
109
+ description,
110
+ canonical,
111
+ noindex: isPrivate,
112
+ nofollow: isPrivate,
113
+ charset: 'UTF-8',
114
+ ogTitle,
115
+ type,
116
+ image: image as string | undefined,
117
+ imageAlt,
118
+ url,
119
+ locale: locale as string,
120
+ localeAlternate,
121
+ languageAlternates,
122
+ siteTitle: defaultsTitle || '',
123
+ twitterCard: twitterCard as string,
124
+ twitterHandle: twitterHandle as string | undefined,
125
+ twitterCreatorHandle: twitterCreatorHandle as string | undefined,
126
+ venue,
127
+ timeStart: time_start,
128
+ timeEnd: time_end,
129
+ }
130
+
131
+ if (transformEntry) {
132
+ output = { ...output, ...transformEntry(entry) }
133
+ }
134
+
135
+ return output
136
+ }
@@ -0,0 +1,70 @@
1
+ import type { SeoEntry } from '../types.js'
2
+ import type { makeGetData } from './getData.js'
3
+
4
+ export const makeGetMetasData =
5
+ (getData: ReturnType<typeof makeGetData>) =>
6
+ (entry: SeoEntry): Record<string, unknown> => {
7
+ const {
8
+ title,
9
+ description,
10
+ canonical,
11
+ noindex,
12
+ nofollow,
13
+ charset,
14
+ ogTitle,
15
+ type,
16
+ authors,
17
+ publishedTime,
18
+ modifiedTime,
19
+ image,
20
+ imageAlt,
21
+ url,
22
+ locale,
23
+ localeAlternate,
24
+ languageAlternates,
25
+ siteTitle,
26
+ twitterCard,
27
+ twitterHandle,
28
+ twitterCreatorHandle,
29
+ } = getData(entry)
30
+
31
+ return {
32
+ title,
33
+ description,
34
+ canonical,
35
+ noindex,
36
+ nofollow,
37
+ charset,
38
+ languageAlternates,
39
+ openGraph: {
40
+ basic: {
41
+ title: ogTitle,
42
+ type,
43
+ image,
44
+ url,
45
+ },
46
+ optional: {
47
+ locale,
48
+ localeAlternate,
49
+ description,
50
+ siteName: siteTitle,
51
+ },
52
+ image: {
53
+ alt: imageAlt,
54
+ },
55
+ ...(type === 'article'
56
+ ? {
57
+ publishedTime,
58
+ modifiedTime,
59
+ authors: authors.map((a) => a.name),
60
+ }
61
+ : {}),
62
+ },
63
+ twitter: {
64
+ description,
65
+ card: twitterCard,
66
+ site: twitterHandle ? '@' + twitterHandle : undefined,
67
+ creator: twitterCreatorHandle ? '@' + twitterCreatorHandle : undefined,
68
+ },
69
+ }
70
+ }
@@ -0,0 +1,16 @@
1
+ import type { SeoEntry } from '../types.js'
2
+ import type { makeGetData } from './getData.js'
3
+ import { parseBase, parseEvent } from './sd.js'
4
+
5
+ export const makeGetStructuredData =
6
+ (getData: ReturnType<typeof makeGetData>) =>
7
+ (entry: SeoEntry): Record<string, unknown> => {
8
+ const data = getData(entry)
9
+ let output = parseBase(data)
10
+
11
+ if (data._type === 'event') {
12
+ output = { ...output, ...parseEvent(data) }
13
+ }
14
+
15
+ return output
16
+ }
@@ -0,0 +1,16 @@
1
+ import type { SeoUserConfig, SeoUtils } from '../types.js'
2
+ import { makeGetData } from './getData.js'
3
+ import { makeGetMetasData } from './getMetasData.js'
4
+ import { makeGetStructuredData } from './getStructuredData.js'
5
+
6
+ export const createSeoUtils = (config: SeoUserConfig): SeoUtils => {
7
+ if (!config?.defaults?.image) {
8
+ throw new Error(
9
+ '@thenewdynamic/astro-seo: `defaults.image` is required. Set a default OG image in your `seo.config.ts` (e.g., `image: "/og-image.png"` or a Sanity image object).'
10
+ )
11
+ }
12
+ const getData = makeGetData(config)
13
+ const getMetasData = makeGetMetasData(getData)
14
+ const getStructuredData = makeGetStructuredData(getData)
15
+ return { getData, getMetasData, getStructuredData }
16
+ }
package/src/core/sd.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { SeoData } from '../types.js'
2
+
3
+ export const parseBase = (data: SeoData): Record<string, unknown> => {
4
+ const { description, ogTitle, type, publishedTime, modifiedTime, image, authors, url } = data
5
+
6
+ return {
7
+ '@context': 'https://schema.org',
8
+ '@type': type,
9
+ headline: ogTitle,
10
+ url,
11
+ image: [image],
12
+ description,
13
+ ...(authors?.length ? {
14
+ author: authors.map(({ name, url }) => ({
15
+ '@type': 'Person',
16
+ name,
17
+ url,
18
+ }))
19
+ } : {}),
20
+ ...(type === 'article' ? {
21
+ datePublished: publishedTime,
22
+ dateModified: modifiedTime,
23
+ } : {}),
24
+ }
25
+ }
26
+
27
+ export const parseVenue = (data: Record<string, unknown>): Record<string, unknown> => {
28
+ const { title, address_1: address, city, country, state, zip } = data as Record<string, string>
29
+ return {
30
+ '@type': 'Place',
31
+ name: title,
32
+ address: {
33
+ '@type': 'PostalAddress',
34
+ streetAddress: address,
35
+ addressLocality: city,
36
+ postalCode: zip,
37
+ addressRegion: state,
38
+ addressCountry: country,
39
+ },
40
+ }
41
+ }
42
+
43
+ export const parseEvent = (data: SeoData): Record<string, unknown> => {
44
+ const { timeStart, timeEnd, venue } = data
45
+ return {
46
+ '@type': 'Event',
47
+ startDate: timeStart,
48
+ endDate: timeEnd,
49
+ ...(venue ? { location: parseVenue(venue as Record<string, unknown>) } : {}),
50
+ }
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { resolve } from 'node:path'
3
+ import type { AstroIntegration } from 'astro'
4
+ import type { Plugin } from 'vite'
5
+ import type { TndSeoOptions } from './types.js'
6
+
7
+ const VIRTUAL_MODULE_ID = 'virtual:tnd/seo'
8
+ const RESOLVED_ID = '\0' + VIRTUAL_MODULE_ID
9
+
10
+ const VIRTUAL_MODULE_TYPES = `
11
+ declare module 'virtual:tnd/seo' {
12
+ import type { SeoEntry, SeoData } from '@thenewdynamic/astro-seo'
13
+ export const getData: (entry: SeoEntry) => SeoData
14
+ export const getMetasData: (entry: SeoEntry) => Record<string, unknown>
15
+ export const getStructuredData: (entry: SeoEntry) => Record<string, unknown>
16
+ }
17
+ `
18
+
19
+ export default function tndSeo(options: TndSeoOptions = {}): AstroIntegration {
20
+ let configFilePath: string
21
+ let coreModulePath: string
22
+ let astroSite: string | undefined
23
+
24
+ return {
25
+ name: '@thenewdynamic/astro-seo',
26
+ hooks: {
27
+ 'astro:config:setup': ({ config, updateConfig }) => {
28
+ astroSite = config.site?.toString()
29
+ const root = fileURLToPath(config.root)
30
+ const userConfigPath = options.configPath ?? './seo.config'
31
+ configFilePath = resolve(root, userConfigPath)
32
+ coreModulePath = fileURLToPath(new URL('./core/index.js', import.meta.url))
33
+
34
+ const plugin: Plugin = {
35
+ name: 'vite-plugin-tnd-seo',
36
+ resolveId(id) {
37
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_ID
38
+ },
39
+ load(id) {
40
+ if (id !== RESOLVED_ID) return
41
+ return [
42
+ `import userConfig from '${configFilePath}'`,
43
+ `import { createSeoUtils } from '${coreModulePath}'`,
44
+ `const astroSite = ${astroSite ? `'${astroSite}'` : 'undefined'}`,
45
+ `const defaultIsProd = () => import.meta.env.PROD`,
46
+ `const config = userConfig || {}`,
47
+ `config.defaults = config.defaults || {}`,
48
+ `if (astroSite && !config.defaults.url) config.defaults.url = astroSite`,
49
+ `if (!config.isProd) config.isProd = defaultIsProd`,
50
+ `export const { getData, getMetasData, getStructuredData } = createSeoUtils(config)`,
51
+ ].join('\n')
52
+ },
53
+ }
54
+
55
+ updateConfig({ vite: { plugins: [plugin] } })
56
+ },
57
+ 'astro:config:done': ({ injectTypes }) => {
58
+ injectTypes({ filename: 'virtual-tnd-seo.d.ts', content: VIRTUAL_MODULE_TYPES })
59
+ },
60
+ },
61
+ }
62
+ }
63
+
64
+ export type { SeoUserConfig, SeoEntry, SeoData, SeoUtils, TndSeoOptions } from './types.js'
65
+ export { flattenEntry } from './utils.js'
package/src/types.ts ADDED
@@ -0,0 +1,92 @@
1
+ export interface DefaultsConfig {
2
+ url?: string
3
+ image: string | Record<string, unknown>
4
+ title?: string
5
+ description?: string
6
+ seo?: {
7
+ title?: string
8
+ twitterHandle?: string
9
+ }
10
+ }
11
+
12
+ export interface ImageOptions {
13
+ width?: number
14
+ height?: number
15
+ [key: string]: unknown
16
+ }
17
+
18
+ export interface SeoEntry {
19
+ _type?: string
20
+ title?: string
21
+ type?: string
22
+ _updatedAt?: string
23
+ time_start?: string
24
+ time_end?: string
25
+ venue?: unknown
26
+ date?: string
27
+ url?: string
28
+ description?: string | unknown[]
29
+ descriptionText?: string
30
+ locale?: string
31
+ image?: string | Record<string, unknown>
32
+ authors?: Array<{ title?: string; name?: string; url?: string }>
33
+ bodyText?: string
34
+ translation?: { url: string; lang: string }
35
+ twitterCard?: string
36
+ twitterHandle?: string
37
+ twitterCreatorHandle?: string
38
+ home?: boolean
39
+ seo?: {
40
+ title?: string
41
+ description?: string
42
+ image?: Record<string, unknown>
43
+ canonical?: string
44
+ private?: boolean
45
+ }
46
+ [key: string]: unknown
47
+ }
48
+
49
+ export interface SeoData {
50
+ _type?: string
51
+ title: string
52
+ publishedTime?: string
53
+ modifiedTime?: string
54
+ authors: Array<{ name: string; url: string | false }>
55
+ description?: string
56
+ canonical?: string | false
57
+ noindex: boolean
58
+ nofollow: boolean
59
+ charset: string
60
+ ogTitle: string
61
+ type: string
62
+ image?: string | false
63
+ imageAlt: string
64
+ url?: string | false
65
+ locale: string
66
+ localeAlternate?: string[]
67
+ languageAlternates?: Array<{ href: string; hrefLang: string }>
68
+ siteTitle: string
69
+ twitterCard: string
70
+ twitterHandle?: string
71
+ twitterCreatorHandle?: string
72
+ venue?: unknown
73
+ timeStart?: string
74
+ timeEnd?: string
75
+ }
76
+
77
+ export interface SeoUserConfig {
78
+ defaults?: DefaultsConfig
79
+ resolveImage?: (image: Record<string, unknown>, opts?: ImageOptions) => string
80
+ transformEntry?: (entry: SeoEntry) => Partial<SeoData>
81
+ isProd?: () => boolean
82
+ }
83
+
84
+ export interface SeoUtils {
85
+ getData: (entry: SeoEntry) => SeoData
86
+ getMetasData: (entry: SeoEntry) => Record<string, unknown>
87
+ getStructuredData: (entry: SeoEntry) => Record<string, unknown>
88
+ }
89
+
90
+ export interface TndSeoOptions {
91
+ configPath?: string
92
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,30 @@
1
+ export const escapeString = (string: string): string => {
2
+ if (/[*_"]/.test(string)) {
3
+ return string.replace('"', '&quot;').replace(/[*_]/g, '')
4
+ }
5
+ return string
6
+ }
7
+
8
+ export const makeAbsUrl = (baseURL: string) => (url: string): string | false => {
9
+ if (typeof url === 'undefined') return false
10
+ const separator = url.charAt(0) !== '/' ? '/' : ''
11
+ return baseURL + separator + url
12
+ }
13
+
14
+ export const isHome = (entry: Record<string, unknown>): boolean => {
15
+ return typeof entry.home !== 'undefined' && !!entry.home
16
+ }
17
+
18
+ export const getExcerpt = (string: string, length = 300): string => {
19
+ if (string && string.length > length) {
20
+ return string.substring(0, length) + '...'
21
+ }
22
+ return string
23
+ }
24
+
25
+ export const flattenEntry = (
26
+ entry: Record<string, unknown> & { data?: Record<string, unknown> }
27
+ ): Record<string, unknown> => ({
28
+ ...entry,
29
+ ...entry.data,
30
+ })