seox 1.0.0 → 1.2.0

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
@@ -5,7 +5,7 @@
5
5
  Seox is an **open source tool** that centralizes and automates **SEO management** (meta tags, Open Graph, JSON-LD...) in **Next.js** projects using the **App Router**.
6
6
  It combines **TypeScript-typed configuration**, **automatic metadata injection**, and an **intuitive CLI** to guide developers.
7
7
 
8
- [![npm version](https://img.shields.io/npm/v/metanext.svg)](https://www.npmjs.com/package/@neysixx/metanext)
8
+ [![npm version](https://img.shields.io/npm/v/seox.svg)](https://www.npmjs.com/package/seox)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
11
11
 
@@ -130,10 +130,24 @@ After running `bunx seox configure`, your files are automatically updated:
130
130
  ```tsx
131
131
  // app/layout.tsx (automatically generated)
132
132
  import { seoConfig } from "@/lib/seo";
133
+ import { JsonLd } from "seox/next";
133
134
 
134
135
  export const metadata = seoConfig.configToMetadata();
136
+
137
+ export default function RootLayout({ children }) {
138
+ return (
139
+ <html lang="en">
140
+ <head>
141
+ <JsonLd seo={seoConfig} />
142
+ </head>
143
+ <body>{children}</body>
144
+ </html>
145
+ );
146
+ }
135
147
  ```
136
148
 
149
+ > 💡 The `<JsonLd />` component automatically injects all JSON-LD structured data configured in your `seoConfig`.
150
+
137
151
  #### Page-specific Customization
138
152
 
139
153
  ```tsx
@@ -189,6 +203,134 @@ Seox merges these fields with the global configuration.
189
203
 
190
204
  ---
191
205
 
206
+ ## 🏗️ JSON-LD Structured Data
207
+
208
+ Seox provides full support for **JSON-LD structured data** to improve your SEO with rich snippets in Google search results.
209
+
210
+ ### Configuration
211
+
212
+ Add your JSON-LD schemas in your `seo.ts` configuration:
213
+
214
+ ```ts
215
+ import { Seox } from "seox/next";
216
+
217
+ export const seoConfig = new Seox({
218
+ name: "My Site",
219
+ url: "https://mysite.com",
220
+ // ... other config
221
+ jsonld: [
222
+ {
223
+ "@context": "https://schema.org",
224
+ "@type": "Organization",
225
+ name: "My Company",
226
+ url: "https://mysite.com",
227
+ logo: "https://mysite.com/logo.png",
228
+ },
229
+ {
230
+ "@context": "https://schema.org",
231
+ "@type": "LocalBusiness",
232
+ name: "My Business",
233
+ address: {
234
+ "@type": "PostalAddress",
235
+ addressLocality: "Paris",
236
+ addressCountry: "FR",
237
+ },
238
+ },
239
+ {
240
+ "@context": "https://schema.org",
241
+ "@type": "FAQPage",
242
+ mainEntity: [
243
+ {
244
+ "@type": "Question",
245
+ name: "What is your service?",
246
+ acceptedAnswer: {
247
+ "@type": "Answer",
248
+ text: "We provide amazing services...",
249
+ },
250
+ },
251
+ ],
252
+ },
253
+ ],
254
+ });
255
+ ```
256
+
257
+ ### Usage with `<JsonLd />` Component
258
+
259
+ The `<JsonLd />` component automatically renders all your JSON-LD schemas as `<script type="application/ld+json">` tags:
260
+
261
+ ```tsx
262
+ // app/layout.tsx
263
+ import { seoConfig } from "@/lib/seo";
264
+ import { JsonLd } from "seox/next";
265
+
266
+ export default function RootLayout({ children }) {
267
+ return (
268
+ <html lang="en">
269
+ <head>
270
+ <JsonLd seo={seoConfig} />
271
+ </head>
272
+ <body>{children}</body>
273
+ </html>
274
+ );
275
+ }
276
+ ```
277
+
278
+ ### Adding Page-specific Schemas
279
+
280
+ You can add additional schemas for specific pages:
281
+
282
+ ```tsx
283
+ // app/product/[id]/page.tsx
284
+ import { seoConfig } from "@/lib/seo";
285
+ import { JsonLd } from "seox/next";
286
+
287
+ export default function ProductPage() {
288
+ const productSchema = {
289
+ "@context": "https://schema.org",
290
+ "@type": "Product",
291
+ name: "Amazing Product",
292
+ price: "99.99",
293
+ };
294
+
295
+ return (
296
+ <>
297
+ <JsonLd seo={seoConfig} additionalSchemas={[productSchema]} />
298
+ {/* Page content */}
299
+ </>
300
+ );
301
+ }
302
+ ```
303
+
304
+ ### Programmatic Access
305
+
306
+ You can also access JSON-LD data programmatically:
307
+
308
+ ```ts
309
+ // Get JSON-LD as objects
310
+ const schemas = seoConfig.getJsonLd();
311
+
312
+ // Get JSON-LD as stringified JSON (ready for injection)
313
+ const jsonStrings = seoConfig.getJsonLdStrings();
314
+ ```
315
+
316
+ ### Supported Schema Types
317
+
318
+ Seox supports all [Schema.org](https://schema.org) types:
319
+
320
+ | Schema Type | Use Case |
321
+ |-------------|----------|
322
+ | `Organization` | Company information |
323
+ | `LocalBusiness` | Local business with address, hours |
324
+ | `FAQPage` | FAQ sections for rich snippets |
325
+ | `Product` | E-commerce products |
326
+ | `Article` | Blog posts and articles |
327
+ | `BreadcrumbList` | Navigation breadcrumbs |
328
+ | `WebSite` | Site-wide search box |
329
+ | `Person` | Author profiles |
330
+ | `Review` / `AggregateRating` | Reviews and ratings |
331
+
332
+ ---
333
+
192
334
  ## 🧰 Built-in CLI
193
335
 
194
336
  Seox provides an intuitive CLI:
@@ -240,8 +382,26 @@ Centralized SEO configuration with complete TypeScript typing.
240
382
  Method that generates Next.js metadata from your configuration.
241
383
  Accepts optional overrides to customize per page.
242
384
 
243
- ### `seoConfig.configToMetadata()`
244
- Direct usage in your Next.js files to generate metadata.
385
+ ### `getJsonLd(additionalSchemas?)`
386
+ Returns the JSON-LD schemas as an array of objects.
387
+ Accepts optional additional schemas to merge with config.
388
+
389
+ ### `getJsonLdStrings(additionalSchemas?)`
390
+ Returns the JSON-LD schemas as stringified JSON strings, ready for injection.
391
+
392
+ ### `<JsonLd />` Component
393
+ React component to inject JSON-LD structured data into the page `<head>`.
394
+
395
+ ```tsx
396
+ import { JsonLd } from "seox/next";
397
+
398
+ <JsonLd seo={seoConfig} additionalSchemas={[...]} />
399
+ ```
400
+
401
+ | Prop | Type | Description |
402
+ |------|------|-------------|
403
+ | `seo` | `Seox` | Your Seox configuration instance |
404
+ | `additionalSchemas` | `SEOJsonLd[]` | Optional additional schemas to include |
245
405
 
246
406
  ---
247
407
 
@@ -259,7 +419,8 @@ Direct usage in your Next.js files to generate metadata.
259
419
  | Multilingual support (`hreflang`) | 🔜 |
260
420
  | Automatic OG image generation | 🔜 |
261
421
  | Predefined templates (`--template blog`) | 🔜 |
262
- | Advanced JSON-LD support | 🔜 |
422
+ | JSON-LD structured data | |
423
+ | `<JsonLd />` React component | ✅ |
263
424
 
264
425
  ---
265
426
 
@@ -340,4 +501,4 @@ MIT © 2025 — Designed for modern Next.js developers 🧑‍💻
340
501
 
341
502
  [⭐ Star us on GitHub](https://github.com/neysixx/seox) • [🐦 Follow on X](https://x.com/ks_nsx) • [📧 Contact](mailto:kylliansenrens3004@gmail.com)
342
503
 
343
- </div>
504
+ </div>
package/dist/cli.cjs CHANGED
@@ -1,57 +1,74 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var commander=require('commander'),e=require('chalk'),u=require('fs-extra'),F=require('ora'),w=require('path'),v=require('prompts');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var e__default=/*#__PURE__*/_interopDefault(e);var u__default=/*#__PURE__*/_interopDefault(u);var F__default=/*#__PURE__*/_interopDefault(F);var w__default=/*#__PURE__*/_interopDefault(w);var v__default=/*#__PURE__*/_interopDefault(v);var m="SEOX";var c="seo.ts";var p=new commander.Command().name("seox").description(`\u{1F9E0} ${m} - Simplified SEO management for Next.js App Router`).version("1.0.0");var C={config:`import { Seox } from "seox/next";
2
+ 'use strict';var commander=require('commander'),w=require('path'),t=require('chalk'),u=require('fs-extra'),P=require('ora'),A=require('prompts');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var w__default=/*#__PURE__*/_interopDefault(w);var t__default=/*#__PURE__*/_interopDefault(t);var u__default=/*#__PURE__*/_interopDefault(u);var P__default=/*#__PURE__*/_interopDefault(P);var A__default=/*#__PURE__*/_interopDefault(A);var m="SEOX";var c="seo.ts";var f=new commander.Command().name("seox").description(`\u{1F9E0} ${m} - Simplified SEO management for Next.js App Router`).version("1.0.0");var N={config:`import { Seox } from "seox/next";
3
3
 
4
4
  export const seoConfig = new Seox({
5
5
  name: "{{siteName}}",
6
6
  url: "{{baseUrl}}",
7
7
  title: {
8
8
  default: "{{siteName}}",
9
- template: "",
9
+ template: "%s | {{siteName}}",
10
10
  },
11
11
  description: "{{siteDescription}}",
12
12
  keywords: [],
13
13
  creator: "",
14
14
  publisher: "",
15
- authors: [
16
- {
17
- name: "",
18
- url: "",
19
- },
20
- ],
21
- manifest: "",
22
- })`};function g(a){return u__default.default.existsSync(process.cwd()+"/src")?w__default.default.join(process.cwd(),"src","lib",a):w__default.default.join(process.cwd(),"lib",a)}function b(a){let s={errors:[],warnings:[],suggestions:[]};return Object.entries(a.pages||{}).forEach(([t,o])=>{o.title?o.title.length>60&&s.warnings.push(`Page "${t}" : title too long (${o.title.length} > 60)`):s.errors.push(`Page "${t}" : title missing`),o.description?o.description.length>160&&s.warnings.push(`Page "${t}" : description too long (${o.description.length} > 160)`):s.errors.push(`Page "${t}" : description missing`),(!o.jsonld||o.jsonld.length===0)&&s.suggestions.push(`Page "${t}" : add JSON-LD to improve indexing`);}),s}async function O(){let a=[],s=u__default.default.existsSync(process.cwd()+"/src")?"src":"",t=w__default.default.join(process.cwd(),s,"app"),o=w__default.default.join(process.cwd(),s,"pages");return await u__default.default.pathExists(t)&&await $(t,a),await u__default.default.pathExists(o)&&await $(o,a),a}async function $(a,s,t){let o=await u__default.default.readdir(a,{withFileTypes:true});for(let n of o){let i=w__default.default.join(a,n.name);if(n.isDirectory())await $(i,s);else if(n.isFile()&&(n.name==="layout.tsx"||n.name==="page.tsx"||n.name==="layout.ts"||n.name==="page.ts")){let r=await u__default.default.readFile(i,"utf8"),l=r.includes("export const metadata"),f=l?M(r):void 0;s.push({path:i,hasMetadata:l,metadataContent:f});}}}function M(a){let s=a.split(`
23
- `),t=[],o=false,n=0;for(let i of s){if(i.includes("export const metadata")){o=true,t.push(i),n=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length;continue}if(o&&(t.push(i),n+=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length,n===0))break}return t.join(`
24
- `)}async function x(a,s=false){let t=await u__default.default.readFile(a,"utf8");if(t.includes("export const metadata")&&!s)return false;let o=t;if(s&&t.includes("export const metadata")&&(o=t.replace(/export const metadata[^;]+;?\s*/gs,"")),!o.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
25
- `,f=o.split(`
26
- `),E=0;for(let y=0;y<f.length;y++)if(f[y].startsWith("import "))E=y+1;else if(f[y].trim()===""&&E>0)break;f.splice(E,0,l),o=f.join(`
27
- `);}let n=`
15
+ authors: [
16
+ {
17
+ name: "",
18
+ url: "",
19
+ },
20
+ ],
21
+ manifest: "",
22
+ // JSON-LD structured data for rich snippets
23
+ jsonld: [
24
+ {
25
+ "@context": "https://schema.org",
26
+ "@type": "Organization",
27
+ name: "{{siteName}}",
28
+ url: "{{baseUrl}}",
29
+ // Add more fields: logo, address, contactPoint, sameAs...
30
+ },
31
+ ],
32
+ })`};function g(n){return u__default.default.existsSync(`${process.cwd()}/src`)?w__default.default.join(process.cwd(),"src","lib",n):w__default.default.join(process.cwd(),"lib",n)}function S(n){let e={errors:[],warnings:[],suggestions:[]};return Object.entries(n.pages||{}).forEach(([s,o])=>{o.title?o.title.length>60&&e.warnings.push(`Page "${s}" : title too long (${o.title.length} > 60)`):e.errors.push(`Page "${s}" : title missing`),o.description?o.description.length>160&&e.warnings.push(`Page "${s}" : description too long (${o.description.length} > 160)`):e.errors.push(`Page "${s}" : description missing`),(!o.jsonld||o.jsonld.length===0)&&e.suggestions.push(`Page "${s}" : add JSON-LD to improve indexing`);}),e}async function O(){let n=[],e=u__default.default.existsSync(`${process.cwd()}/src`)?"src":"",s=w__default.default.join(process.cwd(),e,"app"),o=w__default.default.join(process.cwd(),e,"pages");return await u__default.default.pathExists(s)&&await $(s,n),await u__default.default.pathExists(o)&&await $(o,n),n}async function $(n,e,s){let o=await u__default.default.readdir(n,{withFileTypes:true});for(let i of o){let a=w__default.default.join(n,i.name);if(i.isDirectory())await $(a,e);else if(i.isFile()&&(i.name==="layout.tsx"||i.name==="page.tsx"||i.name==="layout.ts"||i.name==="page.ts")){let r=await u__default.default.readFile(a,"utf8"),l=r.includes("export const metadata"),d=l?v(r):void 0;e.push({path:a,hasMetadata:l,metadataContent:d});}}}function v(n){let e=n.split(`
33
+ `),s=[],o=false,i=0;for(let a of e){if(a.includes("export const metadata")){o=true,s.push(a),i=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length;continue}if(o&&(s.push(a),i+=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length,i===0))break}return s.join(`
34
+ `)}function M(n){return n.endsWith("layout.tsx")||n.endsWith("layout.ts")}function L(n){if(n.includes("<JsonLd"))return n;let e=n;if(e.includes("import { seoConfig } from '@/lib/seo'")&&!e.includes("import { JsonLd }")){let s=e.split(`
35
+ `),o=0;for(let i=0;i<s.length;i++)if(s[i].includes("import { seoConfig } from '@/lib/seo'")){o=i+1;break}s.splice(o,0,"import { JsonLd } from 'seox/next';"),e=s.join(`
36
+ `);}return e.includes("<head>")&&!e.includes("<JsonLd")?e=e.replace(/<head>(\s*)/,`<head>$1<JsonLd seo={seoConfig} />
37
+ $1`):e.includes("<head")&&!e.includes("<JsonLd")?e=e.replace(/(<head[^>]*>)(\s*)/,`$1$2<JsonLd seo={seoConfig} />
38
+ $2`):!e.includes("<head")&&e.includes("<html")&&(e=e.replace(/(<html[^>]*>)(\s*)/,`$1$2<head>
39
+ <JsonLd seo={seoConfig} />
40
+ </head>$2`)),e}async function x(n,e=false){let s=await u__default.default.readFile(n,"utf8");if(s.includes("export const metadata")&&!e)return false;let o=s;if(e&&s.includes("export const metadata")&&(o=s.replace(/export const metadata[^;]+;?\s*/gs,"")),!o.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
41
+ `,d=o.split(`
42
+ `),E=0;for(let y=0;y<d.length;y++)if(d[y].startsWith("import "))E=y+1;else if(d[y].trim()===""&&E>0)break;d.splice(E,0,l),o=d.join(`
43
+ `);}let i=`
28
44
  export const metadata = seoConfig.configToMetadata();
29
- `,i=o.split(`
30
- `),r=i.length;for(let l=0;l<i.length;l++)if(i[l].startsWith("export default")||i[l].startsWith("export function")||i[l].startsWith("export const")&&!i[l].includes("metadata")){r=l;break}return i.splice(r,0,n),o=i.join(`
31
- `),await u__default.default.writeFile(a,o,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async a=>{if(console.log(e__default.default.cyan.bold(`
45
+ `,a=o.split(`
46
+ `),r=a.length;for(let l=0;l<a.length;l++)if(a[l].startsWith("export default")||a[l].startsWith("export function")||a[l].startsWith("export const")&&!a[l].includes("metadata")){r=l;break}return a.splice(r,0,i),o=a.join(`
47
+ `),M(n)&&(o=L(o)),await u__default.default.writeFile(n,o,"utf8"),true}f.command("init").description("Initialize the SEOX SEO configuration").action(async n=>{if(console.log(t__default.default.cyan.bold(`
32
48
  \u{1F9E0} ${m} - Initialization
33
- `)),await u__default.default.pathExists(g(c))){let{overwrite:o}=await v__default.default({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!o){console.log(e__default.default.yellow("\u26A0\uFE0F Initialization canceled"));return}}let s=await v__default.default([{type:"text",name:"siteName",message:"Name of your site :",initial:"My Site"},{type:"text",name:"baseUrl",message:"Base URL (without trailing slash) :",initial:"https://mysite.com",validate:o=>o.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!s.siteName){console.log(e__default.default.red("\u274C Initialization canceled"));return}let t=F__default.default("Creating files...").start();try{await u__default.default.ensureDir(w__default.default.join(process.cwd(),"lib"));let o=C.config;Object.entries(s).forEach(([n,i])=>{o=o.replace(new RegExp(`{{${n}}}`,"g"),i);}),await u__default.default.writeFile(g(c),o,"utf8"),t.succeed("Configuration created successfully!"),console.log(e__default.default.green(`
34
- \u2705 File created : `)+e__default.default.gray(g(c))),console.log(e__default.default.cyan(`
35
- \u{1F4DD} Next step :`)),console.log(e__default.default.white(" bunx seox configure")),console.log();}catch(o){t.fail("Error creating the configuration"),console.error(e__default.default.red(o.message)),process.exit(1);}});p.command("configure").description("Apply SEO configuration to all Next.js pages and layouts").option("--validate","Validate only without generating",false).option("--force","Force overwrite existing metadata without asking",false).action(async a=>{console.log(e__default.default.cyan.bold(`
49
+ `)),await u__default.default.pathExists(g(c))){let{overwrite:o}=await A__default.default({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!o){console.log(t__default.default.yellow("\u26A0\uFE0F Initialization canceled"));return}}let e=await A__default.default([{type:"text",name:"siteName",message:"Name of your site :",initial:"My Site"},{type:"text",name:"baseUrl",message:"Base URL (without trailing slash) :",initial:"https://mysite.com",validate:o=>o.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!e.siteName){console.log(t__default.default.red("\u274C Initialization canceled"));return}let s=P__default.default("Creating files...").start();try{await u__default.default.ensureDir(w__default.default.join(process.cwd(),"lib"));let o=N.config;Object.entries(e).forEach(([i,a])=>{o=o.replace(new RegExp(`{{${i}}}`,"g"),a);}),await u__default.default.writeFile(g(c),o,"utf8"),s.succeed("Configuration created successfully!"),console.log(t__default.default.green(`
50
+ \u2705 File created : `)+t__default.default.gray(g(c))),console.log(t__default.default.cyan(`
51
+ \u{1F4DD} Next step :`)),console.log(t__default.default.white(" bunx seox configure")),console.log();}catch(o){s.fail("Error creating the configuration"),console.error(t__default.default.red(o.message)),process.exit(1);}});f.command("configure").description("Apply SEO configuration to all Next.js pages and layouts").option("--validate","Validate only without generating",false).option("--force","Force overwrite existing metadata without asking",false).action(async n=>{console.log(t__default.default.cyan.bold(`
36
52
  \u2699\uFE0F ${m} - Configuration
37
- `));let s=F__default.default("Reading configuration...").start();try{if(!await u__default.default.pathExists(g(c))){s.fail(c+" file not found"),console.log(e__default.default.yellow(`
38
- \u{1F4A1} Run first: `)+e__default.default.white("bunx seox init"));return}s.text="Scanning Next.js files...";let t=await O();if(t.length===0){s.fail("No Next.js layout or page files found"),console.log(e__default.default.yellow(`
39
- \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}s.succeed(`Found ${t.length} file(s) to process`),console.log(e__default.default.cyan(`
40
- \u{1F4C1} Files found:`)),t.forEach(r=>{let l=r.hasMetadata?e__default.default.yellow("(has metadata)"):e__default.default.green("(no metadata)");console.log(e__default.default.gray(` \u2022 ${r.path} ${l}`));});let o={added:0,overwritten:0,skipped:0,errors:[]},n=t.filter(r=>r.hasMetadata),i=t.filter(r=>!r.hasMetadata);console.log(e__default.default.cyan(`
41
- \u{1F504} Processing files without metadata...`));for(let r of i)try{await x(r.path,!1)&&(o.added++,console.log(e__default.default.green(` \u2713 Added metadata to ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(e__default.default.red(` \u2717 Error in ${r.path}`));}if(n.length>0)if(console.log(e__default.default.yellow(`
42
- \u26A0\uFE0F ${n.length} file(s) already have metadata exports`)),a.force){console.log(e__default.default.cyan(`
43
- \u{1F504} Overwriting all existing metadata (--force)...`));for(let r of n)try{await x(r.path,!0)&&(o.overwritten++,console.log(e__default.default.yellow(` \u2713 Overwritten ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(e__default.default.red(` \u2717 Error in ${r.path}`));}}else {console.log(e__default.default.cyan(`
44
- \u{1F504} Processing files with existing metadata...`));for(let r of n){let l=await v__default.default({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${r.path}?`,initial:!1});if(l.overwrite===void 0){console.log(e__default.default.yellow(`
45
- \u274C Operation cancelled`));break}if(l.overwrite)try{await x(r.path,!0)&&(o.overwritten++,console.log(e__default.default.yellow(` \u2713 Overwritten ${r.path}`)));}catch(f){o.errors.push(`${r.path}: ${f.message}`),console.log(e__default.default.red(` \u2717 Error in ${r.path}`));}else o.skipped++,console.log(e__default.default.gray(` \u25CB Skipped ${r.path}`));}}console.log(e__default.default.green.bold(`
53
+ `));let e=P__default.default("Reading configuration...").start();try{if(!await u__default.default.pathExists(g(c))){e.fail(`${c} file not found`),console.log(t__default.default.yellow(`
54
+ \u{1F4A1} Run first: `)+t__default.default.white("bunx seox init"));return}e.text="Scanning Next.js files...";let s=await O();if(s.length===0){e.fail("No Next.js layout or page files found"),console.log(t__default.default.yellow(`
55
+ \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}e.succeed(`Found ${s.length} file(s) to process`),console.log(t__default.default.cyan(`
56
+ \u{1F4C1} Files found:`)),s.forEach(r=>{let l=r.hasMetadata?t__default.default.yellow("(has metadata)"):t__default.default.green("(no metadata)");console.log(t__default.default.gray(` \u2022 ${r.path} ${l}`));});let o={added:0,overwritten:0,skipped:0,errors:[]},i=s.filter(r=>r.hasMetadata),a=s.filter(r=>!r.hasMetadata);console.log(t__default.default.cyan(`
57
+ \u{1F504} Processing files without metadata...`));for(let r of a)try{await x(r.path,!1)&&(o.added++,console.log(t__default.default.green(` \u2713 Added metadata to ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(t__default.default.red(` \u2717 Error in ${r.path}`));}if(i.length>0)if(console.log(t__default.default.yellow(`
58
+ \u26A0\uFE0F ${i.length} file(s) already have metadata exports`)),n.force){console.log(t__default.default.cyan(`
59
+ \u{1F504} Overwriting all existing metadata (--force)...`));for(let r of i)try{await x(r.path,!0)&&(o.overwritten++,console.log(t__default.default.yellow(` \u2713 Overwritten ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(t__default.default.red(` \u2717 Error in ${r.path}`));}}else {console.log(t__default.default.cyan(`
60
+ \u{1F504} Processing files with existing metadata...`));for(let r of i){let l=await A__default.default({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${r.path}?`,initial:!1});if(l.overwrite===void 0){console.log(t__default.default.yellow(`
61
+ \u274C Operation cancelled`));break}if(l.overwrite)try{await x(r.path,!0)&&(o.overwritten++,console.log(t__default.default.yellow(` \u2713 Overwritten ${r.path}`)));}catch(d){o.errors.push(`${r.path}: ${d.message}`),console.log(t__default.default.red(` \u2717 Error in ${r.path}`));}else o.skipped++,console.log(t__default.default.gray(` \u25CB Skipped ${r.path}`));}}console.log(t__default.default.green.bold(`
46
62
  \u2705 Configuration Summary:
47
- `)),console.log(e__default.default.gray(` \u2022 ${o.added} file(s) with metadata added`)),o.overwritten>0&&console.log(e__default.default.yellow(` \u2022 ${o.overwritten} file(s) overwritten`)),o.skipped>0&&console.log(e__default.default.gray(` \u2022 ${o.skipped} file(s) skipped`)),o.errors.length>0&&(console.log(e__default.default.red(` \u2022 ${o.errors.length} error(s):`)),o.errors.forEach(r=>{console.log(e__default.default.red(` - ${r}`));})),(o.added>0||o.overwritten>0)&&(console.log(e__default.default.cyan.bold(`
63
+ `)),console.log(t__default.default.gray(` \u2022 ${o.added} file(s) with metadata added`)),o.overwritten>0&&console.log(t__default.default.yellow(` \u2022 ${o.overwritten} file(s) overwritten`)),o.skipped>0&&console.log(t__default.default.gray(` \u2022 ${o.skipped} file(s) skipped`)),o.errors.length>0&&(console.log(t__default.default.red(` \u2022 ${o.errors.length} error(s):`)),o.errors.forEach(r=>{console.log(t__default.default.red(` - ${r}`));})),(o.added>0||o.overwritten>0)&&(console.log(t__default.default.cyan.bold(`
48
64
  \u{1F4DD} Next Steps:
49
- `)),console.log(e__default.default.white(" The following metadata export has been added to your files:")),console.log(e__default.default.gray(" import { seoConfig } from '@/lib/seo';")),console.log(e__default.default.gray(` export const metadata = seoConfig.configToMetadata();
50
- `)),console.log(e__default.default.white(" To customize metadata for specific pages, you can:")),console.log(e__default.default.gray(" 1. Pass overrides to configToMetadata():")),console.log(e__default.default.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(e__default.default.gray(' title: "Custom Page Title",')),console.log(e__default.default.gray(' description: "Custom description"')),console.log(e__default.default.gray(` });
51
- `)),console.log(e__default.default.gray(" 2. Or manually modify the metadata object after generation")),console.log());}catch(t){s.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(e__default.default.red(t.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(e__default.default.cyan.bold(`
65
+ `)),console.log(t__default.default.white(" The following has been added to your files:")),console.log(t__default.default.gray(" import { seoConfig } from '@/lib/seo';")),console.log(t__default.default.gray(" import { JsonLd } from 'seox/next';")),console.log(t__default.default.gray(` export const metadata = seoConfig.configToMetadata();
66
+ `)),console.log(t__default.default.white(" For layout files, the <JsonLd /> component was also added:")),console.log(t__default.default.gray(" <head>")),console.log(t__default.default.gray(" <JsonLd seo={seoConfig} />")),console.log(t__default.default.gray(` </head>
67
+ `)),console.log(t__default.default.white(" To customize metadata for specific pages, you can:")),console.log(t__default.default.gray(" 1. Pass overrides to configToMetadata():")),console.log(t__default.default.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(t__default.default.gray(' title: "Custom Page Title",')),console.log(t__default.default.gray(' description: "Custom description"')),console.log(t__default.default.gray(` });
68
+ `)),console.log(t__default.default.gray(" 2. Add JSON-LD schemas in your seo.ts config")),console.log());}catch(s){e.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(t__default.default.red(s.message)),process.exit(1);}});f.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(t__default.default.cyan.bold(`
52
69
  \u{1FA7A} ${m} - SEO Diagnostic
53
- `));let a=F__default.default("Analyzing...").start();try{if(!await u__default.default.pathExists(g(c))){a.fail(c+" file not found");return}let t=(await import(g(c))).default;a.stop();let o=b(t);if(o.errors.length===0&&o.warnings.length===0){console.log(e__default.default.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
54
- `));return}o.errors.length>0&&(console.log(e__default.default.red.bold(`\u274C ERRORS :
55
- `)),o.errors.map(n=>console.log(e__default.default.red(` \u2022 ${n}`))),console.log()),o.warnings.length>0&&(console.log(e__default.default.yellow.bold(`\u26A0\uFE0F WARNINGS :
56
- `)),o.warnings.map(n=>console.log(e__default.default.yellow(` \u2022 ${n}`))),console.log()),o.suggestions.length>0&&(console.log(e__default.default.cyan.bold(`\u{1F4A1} SUGGESTIONS :
57
- `)),o.suggestions.map(n=>console.log(e__default.default.cyan(` \u2022 ${n}`))),console.log());}catch(s){a.fail("Error during diagnosis"),console.error(e__default.default.red(s.message));}});p.parse(process.argv);var xo=p;module.exports=xo;
70
+ `));let n=P__default.default("Analyzing...").start();try{if(!await u__default.default.pathExists(g(c))){n.fail(`${c} file not found`);return}let s=(await import(g(c))).default;n.stop();let o=S(s);if(o.errors.length===0&&o.warnings.length===0){console.log(t__default.default.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
71
+ `));return}o.errors.length>0&&(console.log(t__default.default.red.bold(`\u274C ERRORS :
72
+ `)),o.errors.map(i=>console.log(t__default.default.red(` \u2022 ${i}`))),console.log()),o.warnings.length>0&&(console.log(t__default.default.yellow.bold(`\u26A0\uFE0F WARNINGS :
73
+ `)),o.warnings.map(i=>console.log(t__default.default.yellow(` \u2022 ${i}`))),console.log()),o.suggestions.length>0&&(console.log(t__default.default.cyan.bold(`\u{1F4A1} SUGGESTIONS :
74
+ `)),o.suggestions.map(i=>console.log(t__default.default.cyan(` \u2022 ${i}`))),console.log());}catch(e){n.fail("Error during diagnosis"),console.error(t__default.default.red(e.message));}});f.parse(process.argv);var $o=f;module.exports=$o;
package/dist/cli.js CHANGED
@@ -1,57 +1,74 @@
1
1
  #!/usr/bin/env node
2
- import {Command}from'commander';import e from'chalk';import u from'fs-extra';import F from'ora';import w from'path';import v from'prompts';var m="SEOX";var c="seo.ts";var p=new Command().name("seox").description(`\u{1F9E0} ${m} - Simplified SEO management for Next.js App Router`).version("1.0.0");var C={config:`import { Seox } from "seox/next";
2
+ import {Command}from'commander';import w from'path';import t from'chalk';import u from'fs-extra';import P from'ora';import A from'prompts';var m="SEOX";var c="seo.ts";var f=new Command().name("seox").description(`\u{1F9E0} ${m} - Simplified SEO management for Next.js App Router`).version("1.0.0");var N={config:`import { Seox } from "seox/next";
3
3
 
4
4
  export const seoConfig = new Seox({
5
5
  name: "{{siteName}}",
6
6
  url: "{{baseUrl}}",
7
7
  title: {
8
8
  default: "{{siteName}}",
9
- template: "",
9
+ template: "%s | {{siteName}}",
10
10
  },
11
11
  description: "{{siteDescription}}",
12
12
  keywords: [],
13
13
  creator: "",
14
14
  publisher: "",
15
- authors: [
16
- {
17
- name: "",
18
- url: "",
19
- },
20
- ],
21
- manifest: "",
22
- })`};function g(a){return u.existsSync(process.cwd()+"/src")?w.join(process.cwd(),"src","lib",a):w.join(process.cwd(),"lib",a)}function b(a){let s={errors:[],warnings:[],suggestions:[]};return Object.entries(a.pages||{}).forEach(([t,o])=>{o.title?o.title.length>60&&s.warnings.push(`Page "${t}" : title too long (${o.title.length} > 60)`):s.errors.push(`Page "${t}" : title missing`),o.description?o.description.length>160&&s.warnings.push(`Page "${t}" : description too long (${o.description.length} > 160)`):s.errors.push(`Page "${t}" : description missing`),(!o.jsonld||o.jsonld.length===0)&&s.suggestions.push(`Page "${t}" : add JSON-LD to improve indexing`);}),s}async function O(){let a=[],s=u.existsSync(process.cwd()+"/src")?"src":"",t=w.join(process.cwd(),s,"app"),o=w.join(process.cwd(),s,"pages");return await u.pathExists(t)&&await $(t,a),await u.pathExists(o)&&await $(o,a),a}async function $(a,s,t){let o=await u.readdir(a,{withFileTypes:true});for(let n of o){let i=w.join(a,n.name);if(n.isDirectory())await $(i,s);else if(n.isFile()&&(n.name==="layout.tsx"||n.name==="page.tsx"||n.name==="layout.ts"||n.name==="page.ts")){let r=await u.readFile(i,"utf8"),l=r.includes("export const metadata"),f=l?M(r):void 0;s.push({path:i,hasMetadata:l,metadataContent:f});}}}function M(a){let s=a.split(`
23
- `),t=[],o=false,n=0;for(let i of s){if(i.includes("export const metadata")){o=true,t.push(i),n=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length;continue}if(o&&(t.push(i),n+=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length,n===0))break}return t.join(`
24
- `)}async function x(a,s=false){let t=await u.readFile(a,"utf8");if(t.includes("export const metadata")&&!s)return false;let o=t;if(s&&t.includes("export const metadata")&&(o=t.replace(/export const metadata[^;]+;?\s*/gs,"")),!o.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
25
- `,f=o.split(`
26
- `),E=0;for(let y=0;y<f.length;y++)if(f[y].startsWith("import "))E=y+1;else if(f[y].trim()===""&&E>0)break;f.splice(E,0,l),o=f.join(`
27
- `);}let n=`
15
+ authors: [
16
+ {
17
+ name: "",
18
+ url: "",
19
+ },
20
+ ],
21
+ manifest: "",
22
+ // JSON-LD structured data for rich snippets
23
+ jsonld: [
24
+ {
25
+ "@context": "https://schema.org",
26
+ "@type": "Organization",
27
+ name: "{{siteName}}",
28
+ url: "{{baseUrl}}",
29
+ // Add more fields: logo, address, contactPoint, sameAs...
30
+ },
31
+ ],
32
+ })`};function g(n){return u.existsSync(`${process.cwd()}/src`)?w.join(process.cwd(),"src","lib",n):w.join(process.cwd(),"lib",n)}function S(n){let e={errors:[],warnings:[],suggestions:[]};return Object.entries(n.pages||{}).forEach(([s,o])=>{o.title?o.title.length>60&&e.warnings.push(`Page "${s}" : title too long (${o.title.length} > 60)`):e.errors.push(`Page "${s}" : title missing`),o.description?o.description.length>160&&e.warnings.push(`Page "${s}" : description too long (${o.description.length} > 160)`):e.errors.push(`Page "${s}" : description missing`),(!o.jsonld||o.jsonld.length===0)&&e.suggestions.push(`Page "${s}" : add JSON-LD to improve indexing`);}),e}async function O(){let n=[],e=u.existsSync(`${process.cwd()}/src`)?"src":"",s=w.join(process.cwd(),e,"app"),o=w.join(process.cwd(),e,"pages");return await u.pathExists(s)&&await $(s,n),await u.pathExists(o)&&await $(o,n),n}async function $(n,e,s){let o=await u.readdir(n,{withFileTypes:true});for(let i of o){let a=w.join(n,i.name);if(i.isDirectory())await $(a,e);else if(i.isFile()&&(i.name==="layout.tsx"||i.name==="page.tsx"||i.name==="layout.ts"||i.name==="page.ts")){let r=await u.readFile(a,"utf8"),l=r.includes("export const metadata"),d=l?v(r):void 0;e.push({path:a,hasMetadata:l,metadataContent:d});}}}function v(n){let e=n.split(`
33
+ `),s=[],o=false,i=0;for(let a of e){if(a.includes("export const metadata")){o=true,s.push(a),i=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length;continue}if(o&&(s.push(a),i+=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length,i===0))break}return s.join(`
34
+ `)}function M(n){return n.endsWith("layout.tsx")||n.endsWith("layout.ts")}function L(n){if(n.includes("<JsonLd"))return n;let e=n;if(e.includes("import { seoConfig } from '@/lib/seo'")&&!e.includes("import { JsonLd }")){let s=e.split(`
35
+ `),o=0;for(let i=0;i<s.length;i++)if(s[i].includes("import { seoConfig } from '@/lib/seo'")){o=i+1;break}s.splice(o,0,"import { JsonLd } from 'seox/next';"),e=s.join(`
36
+ `);}return e.includes("<head>")&&!e.includes("<JsonLd")?e=e.replace(/<head>(\s*)/,`<head>$1<JsonLd seo={seoConfig} />
37
+ $1`):e.includes("<head")&&!e.includes("<JsonLd")?e=e.replace(/(<head[^>]*>)(\s*)/,`$1$2<JsonLd seo={seoConfig} />
38
+ $2`):!e.includes("<head")&&e.includes("<html")&&(e=e.replace(/(<html[^>]*>)(\s*)/,`$1$2<head>
39
+ <JsonLd seo={seoConfig} />
40
+ </head>$2`)),e}async function x(n,e=false){let s=await u.readFile(n,"utf8");if(s.includes("export const metadata")&&!e)return false;let o=s;if(e&&s.includes("export const metadata")&&(o=s.replace(/export const metadata[^;]+;?\s*/gs,"")),!o.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
41
+ `,d=o.split(`
42
+ `),E=0;for(let y=0;y<d.length;y++)if(d[y].startsWith("import "))E=y+1;else if(d[y].trim()===""&&E>0)break;d.splice(E,0,l),o=d.join(`
43
+ `);}let i=`
28
44
  export const metadata = seoConfig.configToMetadata();
29
- `,i=o.split(`
30
- `),r=i.length;for(let l=0;l<i.length;l++)if(i[l].startsWith("export default")||i[l].startsWith("export function")||i[l].startsWith("export const")&&!i[l].includes("metadata")){r=l;break}return i.splice(r,0,n),o=i.join(`
31
- `),await u.writeFile(a,o,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async a=>{if(console.log(e.cyan.bold(`
45
+ `,a=o.split(`
46
+ `),r=a.length;for(let l=0;l<a.length;l++)if(a[l].startsWith("export default")||a[l].startsWith("export function")||a[l].startsWith("export const")&&!a[l].includes("metadata")){r=l;break}return a.splice(r,0,i),o=a.join(`
47
+ `),M(n)&&(o=L(o)),await u.writeFile(n,o,"utf8"),true}f.command("init").description("Initialize the SEOX SEO configuration").action(async n=>{if(console.log(t.cyan.bold(`
32
48
  \u{1F9E0} ${m} - Initialization
33
- `)),await u.pathExists(g(c))){let{overwrite:o}=await v({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!o){console.log(e.yellow("\u26A0\uFE0F Initialization canceled"));return}}let s=await v([{type:"text",name:"siteName",message:"Name of your site :",initial:"My Site"},{type:"text",name:"baseUrl",message:"Base URL (without trailing slash) :",initial:"https://mysite.com",validate:o=>o.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!s.siteName){console.log(e.red("\u274C Initialization canceled"));return}let t=F("Creating files...").start();try{await u.ensureDir(w.join(process.cwd(),"lib"));let o=C.config;Object.entries(s).forEach(([n,i])=>{o=o.replace(new RegExp(`{{${n}}}`,"g"),i);}),await u.writeFile(g(c),o,"utf8"),t.succeed("Configuration created successfully!"),console.log(e.green(`
34
- \u2705 File created : `)+e.gray(g(c))),console.log(e.cyan(`
35
- \u{1F4DD} Next step :`)),console.log(e.white(" bunx seox configure")),console.log();}catch(o){t.fail("Error creating the configuration"),console.error(e.red(o.message)),process.exit(1);}});p.command("configure").description("Apply SEO configuration to all Next.js pages and layouts").option("--validate","Validate only without generating",false).option("--force","Force overwrite existing metadata without asking",false).action(async a=>{console.log(e.cyan.bold(`
49
+ `)),await u.pathExists(g(c))){let{overwrite:o}=await A({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!o){console.log(t.yellow("\u26A0\uFE0F Initialization canceled"));return}}let e=await A([{type:"text",name:"siteName",message:"Name of your site :",initial:"My Site"},{type:"text",name:"baseUrl",message:"Base URL (without trailing slash) :",initial:"https://mysite.com",validate:o=>o.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!e.siteName){console.log(t.red("\u274C Initialization canceled"));return}let s=P("Creating files...").start();try{await u.ensureDir(w.join(process.cwd(),"lib"));let o=N.config;Object.entries(e).forEach(([i,a])=>{o=o.replace(new RegExp(`{{${i}}}`,"g"),a);}),await u.writeFile(g(c),o,"utf8"),s.succeed("Configuration created successfully!"),console.log(t.green(`
50
+ \u2705 File created : `)+t.gray(g(c))),console.log(t.cyan(`
51
+ \u{1F4DD} Next step :`)),console.log(t.white(" bunx seox configure")),console.log();}catch(o){s.fail("Error creating the configuration"),console.error(t.red(o.message)),process.exit(1);}});f.command("configure").description("Apply SEO configuration to all Next.js pages and layouts").option("--validate","Validate only without generating",false).option("--force","Force overwrite existing metadata without asking",false).action(async n=>{console.log(t.cyan.bold(`
36
52
  \u2699\uFE0F ${m} - Configuration
37
- `));let s=F("Reading configuration...").start();try{if(!await u.pathExists(g(c))){s.fail(c+" file not found"),console.log(e.yellow(`
38
- \u{1F4A1} Run first: `)+e.white("bunx seox init"));return}s.text="Scanning Next.js files...";let t=await O();if(t.length===0){s.fail("No Next.js layout or page files found"),console.log(e.yellow(`
39
- \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}s.succeed(`Found ${t.length} file(s) to process`),console.log(e.cyan(`
40
- \u{1F4C1} Files found:`)),t.forEach(r=>{let l=r.hasMetadata?e.yellow("(has metadata)"):e.green("(no metadata)");console.log(e.gray(` \u2022 ${r.path} ${l}`));});let o={added:0,overwritten:0,skipped:0,errors:[]},n=t.filter(r=>r.hasMetadata),i=t.filter(r=>!r.hasMetadata);console.log(e.cyan(`
41
- \u{1F504} Processing files without metadata...`));for(let r of i)try{await x(r.path,!1)&&(o.added++,console.log(e.green(` \u2713 Added metadata to ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(e.red(` \u2717 Error in ${r.path}`));}if(n.length>0)if(console.log(e.yellow(`
42
- \u26A0\uFE0F ${n.length} file(s) already have metadata exports`)),a.force){console.log(e.cyan(`
43
- \u{1F504} Overwriting all existing metadata (--force)...`));for(let r of n)try{await x(r.path,!0)&&(o.overwritten++,console.log(e.yellow(` \u2713 Overwritten ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(e.red(` \u2717 Error in ${r.path}`));}}else {console.log(e.cyan(`
44
- \u{1F504} Processing files with existing metadata...`));for(let r of n){let l=await v({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${r.path}?`,initial:!1});if(l.overwrite===void 0){console.log(e.yellow(`
45
- \u274C Operation cancelled`));break}if(l.overwrite)try{await x(r.path,!0)&&(o.overwritten++,console.log(e.yellow(` \u2713 Overwritten ${r.path}`)));}catch(f){o.errors.push(`${r.path}: ${f.message}`),console.log(e.red(` \u2717 Error in ${r.path}`));}else o.skipped++,console.log(e.gray(` \u25CB Skipped ${r.path}`));}}console.log(e.green.bold(`
53
+ `));let e=P("Reading configuration...").start();try{if(!await u.pathExists(g(c))){e.fail(`${c} file not found`),console.log(t.yellow(`
54
+ \u{1F4A1} Run first: `)+t.white("bunx seox init"));return}e.text="Scanning Next.js files...";let s=await O();if(s.length===0){e.fail("No Next.js layout or page files found"),console.log(t.yellow(`
55
+ \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}e.succeed(`Found ${s.length} file(s) to process`),console.log(t.cyan(`
56
+ \u{1F4C1} Files found:`)),s.forEach(r=>{let l=r.hasMetadata?t.yellow("(has metadata)"):t.green("(no metadata)");console.log(t.gray(` \u2022 ${r.path} ${l}`));});let o={added:0,overwritten:0,skipped:0,errors:[]},i=s.filter(r=>r.hasMetadata),a=s.filter(r=>!r.hasMetadata);console.log(t.cyan(`
57
+ \u{1F504} Processing files without metadata...`));for(let r of a)try{await x(r.path,!1)&&(o.added++,console.log(t.green(` \u2713 Added metadata to ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(t.red(` \u2717 Error in ${r.path}`));}if(i.length>0)if(console.log(t.yellow(`
58
+ \u26A0\uFE0F ${i.length} file(s) already have metadata exports`)),n.force){console.log(t.cyan(`
59
+ \u{1F504} Overwriting all existing metadata (--force)...`));for(let r of i)try{await x(r.path,!0)&&(o.overwritten++,console.log(t.yellow(` \u2713 Overwritten ${r.path}`)));}catch(l){o.errors.push(`${r.path}: ${l.message}`),console.log(t.red(` \u2717 Error in ${r.path}`));}}else {console.log(t.cyan(`
60
+ \u{1F504} Processing files with existing metadata...`));for(let r of i){let l=await A({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${r.path}?`,initial:!1});if(l.overwrite===void 0){console.log(t.yellow(`
61
+ \u274C Operation cancelled`));break}if(l.overwrite)try{await x(r.path,!0)&&(o.overwritten++,console.log(t.yellow(` \u2713 Overwritten ${r.path}`)));}catch(d){o.errors.push(`${r.path}: ${d.message}`),console.log(t.red(` \u2717 Error in ${r.path}`));}else o.skipped++,console.log(t.gray(` \u25CB Skipped ${r.path}`));}}console.log(t.green.bold(`
46
62
  \u2705 Configuration Summary:
47
- `)),console.log(e.gray(` \u2022 ${o.added} file(s) with metadata added`)),o.overwritten>0&&console.log(e.yellow(` \u2022 ${o.overwritten} file(s) overwritten`)),o.skipped>0&&console.log(e.gray(` \u2022 ${o.skipped} file(s) skipped`)),o.errors.length>0&&(console.log(e.red(` \u2022 ${o.errors.length} error(s):`)),o.errors.forEach(r=>{console.log(e.red(` - ${r}`));})),(o.added>0||o.overwritten>0)&&(console.log(e.cyan.bold(`
63
+ `)),console.log(t.gray(` \u2022 ${o.added} file(s) with metadata added`)),o.overwritten>0&&console.log(t.yellow(` \u2022 ${o.overwritten} file(s) overwritten`)),o.skipped>0&&console.log(t.gray(` \u2022 ${o.skipped} file(s) skipped`)),o.errors.length>0&&(console.log(t.red(` \u2022 ${o.errors.length} error(s):`)),o.errors.forEach(r=>{console.log(t.red(` - ${r}`));})),(o.added>0||o.overwritten>0)&&(console.log(t.cyan.bold(`
48
64
  \u{1F4DD} Next Steps:
49
- `)),console.log(e.white(" The following metadata export has been added to your files:")),console.log(e.gray(" import { seoConfig } from '@/lib/seo';")),console.log(e.gray(` export const metadata = seoConfig.configToMetadata();
50
- `)),console.log(e.white(" To customize metadata for specific pages, you can:")),console.log(e.gray(" 1. Pass overrides to configToMetadata():")),console.log(e.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(e.gray(' title: "Custom Page Title",')),console.log(e.gray(' description: "Custom description"')),console.log(e.gray(` });
51
- `)),console.log(e.gray(" 2. Or manually modify the metadata object after generation")),console.log());}catch(t){s.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(e.red(t.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(e.cyan.bold(`
65
+ `)),console.log(t.white(" The following has been added to your files:")),console.log(t.gray(" import { seoConfig } from '@/lib/seo';")),console.log(t.gray(" import { JsonLd } from 'seox/next';")),console.log(t.gray(` export const metadata = seoConfig.configToMetadata();
66
+ `)),console.log(t.white(" For layout files, the <JsonLd /> component was also added:")),console.log(t.gray(" <head>")),console.log(t.gray(" <JsonLd seo={seoConfig} />")),console.log(t.gray(` </head>
67
+ `)),console.log(t.white(" To customize metadata for specific pages, you can:")),console.log(t.gray(" 1. Pass overrides to configToMetadata():")),console.log(t.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(t.gray(' title: "Custom Page Title",')),console.log(t.gray(' description: "Custom description"')),console.log(t.gray(` });
68
+ `)),console.log(t.gray(" 2. Add JSON-LD schemas in your seo.ts config")),console.log());}catch(s){e.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(t.red(s.message)),process.exit(1);}});f.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(t.cyan.bold(`
52
69
  \u{1FA7A} ${m} - SEO Diagnostic
53
- `));let a=F("Analyzing...").start();try{if(!await u.pathExists(g(c))){a.fail(c+" file not found");return}let t=(await import(g(c))).default;a.stop();let o=b(t);if(o.errors.length===0&&o.warnings.length===0){console.log(e.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
54
- `));return}o.errors.length>0&&(console.log(e.red.bold(`\u274C ERRORS :
55
- `)),o.errors.map(n=>console.log(e.red(` \u2022 ${n}`))),console.log()),o.warnings.length>0&&(console.log(e.yellow.bold(`\u26A0\uFE0F WARNINGS :
56
- `)),o.warnings.map(n=>console.log(e.yellow(` \u2022 ${n}`))),console.log()),o.suggestions.length>0&&(console.log(e.cyan.bold(`\u{1F4A1} SUGGESTIONS :
57
- `)),o.suggestions.map(n=>console.log(e.cyan(` \u2022 ${n}`))),console.log());}catch(s){a.fail("Error during diagnosis"),console.error(e.red(s.message));}});p.parse(process.argv);var xo=p;export{xo as default};
70
+ `));let n=P("Analyzing...").start();try{if(!await u.pathExists(g(c))){n.fail(`${c} file not found`);return}let s=(await import(g(c))).default;n.stop();let o=S(s);if(o.errors.length===0&&o.warnings.length===0){console.log(t.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
71
+ `));return}o.errors.length>0&&(console.log(t.red.bold(`\u274C ERRORS :
72
+ `)),o.errors.map(i=>console.log(t.red(` \u2022 ${i}`))),console.log()),o.warnings.length>0&&(console.log(t.yellow.bold(`\u26A0\uFE0F WARNINGS :
73
+ `)),o.warnings.map(i=>console.log(t.yellow(` \u2022 ${i}`))),console.log()),o.suggestions.length>0&&(console.log(t.cyan.bold(`\u{1F4A1} SUGGESTIONS :
74
+ `)),o.suggestions.map(i=>console.log(t.cyan(` \u2022 ${i}`))),console.log());}catch(e){n.fail("Error during diagnosis"),console.error(t.red(e.message));}});f.parse(process.argv);var $o=f;export{$o as default};
package/dist/index.d.cts CHANGED
@@ -1,87 +1,27 @@
1
- import { TemplateString } from 'next/dist/lib/metadata/types/metadata-types';
2
- import { OpenGraphType } from 'next/dist/lib/metadata/types/opengraph-types';
1
+ import { Metadata } from 'next';
3
2
 
4
- interface SEOAuthor {
5
- name: string;
6
- url?: string;
7
- }
8
- interface SEOImage {
9
- url: string;
10
- width?: number;
11
- height?: number;
12
- alt?: string;
13
- }
14
- interface SEORobots {
15
- index?: boolean;
16
- follow?: boolean;
17
- googleBot?: {
18
- index?: boolean;
19
- follow?: boolean;
20
- 'max-video-preview'?: number;
21
- 'max-image-preview'?: 'none' | 'standard' | 'large';
22
- 'max-snippet'?: number;
23
- };
24
- }
25
- interface SEOTwitter {
26
- card?: 'summary' | 'summary_large_image' | 'app' | 'player';
27
- title?: string;
28
- description?: string;
29
- images?: string[];
30
- creator?: string;
31
- }
32
- interface SEOOpenGraph {
33
- type: OpenGraphType;
34
- locale?: string;
35
- url?: string;
36
- title?: string;
37
- description?: string;
38
- siteName?: string;
39
- images?: SEOImage[];
40
- }
41
- interface SEOFormatDetection {
42
- email?: boolean;
43
- address?: boolean;
44
- telephone?: boolean;
45
- }
46
- interface SEOIcons {
47
- icon?: {
48
- url: string;
49
- sizes?: string;
50
- }[];
51
- apple?: {
52
- url: string;
53
- sizes?: string;
54
- }[];
55
- shortcut?: {
56
- url: string;
57
- sizes?: string;
58
- }[];
59
- }
3
+ /**
4
+ * JSON-LD structured data schema
5
+ */
60
6
  interface SEOJsonLd {
61
7
  '@context': string;
62
8
  '@type': string;
63
- [key: string]: any;
9
+ [key: string]: unknown;
64
10
  }
65
11
  /**
66
- * Main SEO structure for site or page-level configuration
12
+ * SEO configuration extending Next.js Metadata with additional fields
13
+ *
14
+ * - `name`: Site name (used as fallback for openGraph.siteName)
15
+ * - `url`: Site URL (used as fallback for openGraph.url)
16
+ * - `jsonld`: JSON-LD structured data schemas
67
17
  */
68
- interface SEOConfig {
18
+ interface SEOConfig extends Metadata {
19
+ /** Site name, used as fallback for openGraph.siteName */
69
20
  name: string;
21
+ /** Site URL, used as fallback for openGraph.url */
70
22
  url: string;
71
- title?: TemplateString | string;
72
- description?: string;
73
- keywords?: string[];
74
- creator?: string;
75
- publisher?: string;
76
- authors?: SEOAuthor[];
77
- manifest?: string;
78
- icons?: SEOIcons;
79
- formatDetection?: SEOFormatDetection;
80
- openGraph?: SEOOpenGraph;
81
- twitter?: SEOTwitter;
82
- robots?: SEORobots;
23
+ /** JSON-LD structured data schemas */
83
24
  jsonld?: SEOJsonLd[];
84
- locale?: string;
85
25
  }
86
26
 
87
- export type { SEOAuthor, SEOConfig, SEOFormatDetection, SEOIcons, SEOImage, SEOJsonLd, SEOOpenGraph, SEORobots, SEOTwitter };
27
+ export type { SEOConfig, SEOJsonLd };
package/dist/index.d.ts CHANGED
@@ -1,87 +1,27 @@
1
- import { TemplateString } from 'next/dist/lib/metadata/types/metadata-types';
2
- import { OpenGraphType } from 'next/dist/lib/metadata/types/opengraph-types';
1
+ import { Metadata } from 'next';
3
2
 
4
- interface SEOAuthor {
5
- name: string;
6
- url?: string;
7
- }
8
- interface SEOImage {
9
- url: string;
10
- width?: number;
11
- height?: number;
12
- alt?: string;
13
- }
14
- interface SEORobots {
15
- index?: boolean;
16
- follow?: boolean;
17
- googleBot?: {
18
- index?: boolean;
19
- follow?: boolean;
20
- 'max-video-preview'?: number;
21
- 'max-image-preview'?: 'none' | 'standard' | 'large';
22
- 'max-snippet'?: number;
23
- };
24
- }
25
- interface SEOTwitter {
26
- card?: 'summary' | 'summary_large_image' | 'app' | 'player';
27
- title?: string;
28
- description?: string;
29
- images?: string[];
30
- creator?: string;
31
- }
32
- interface SEOOpenGraph {
33
- type: OpenGraphType;
34
- locale?: string;
35
- url?: string;
36
- title?: string;
37
- description?: string;
38
- siteName?: string;
39
- images?: SEOImage[];
40
- }
41
- interface SEOFormatDetection {
42
- email?: boolean;
43
- address?: boolean;
44
- telephone?: boolean;
45
- }
46
- interface SEOIcons {
47
- icon?: {
48
- url: string;
49
- sizes?: string;
50
- }[];
51
- apple?: {
52
- url: string;
53
- sizes?: string;
54
- }[];
55
- shortcut?: {
56
- url: string;
57
- sizes?: string;
58
- }[];
59
- }
3
+ /**
4
+ * JSON-LD structured data schema
5
+ */
60
6
  interface SEOJsonLd {
61
7
  '@context': string;
62
8
  '@type': string;
63
- [key: string]: any;
9
+ [key: string]: unknown;
64
10
  }
65
11
  /**
66
- * Main SEO structure for site or page-level configuration
12
+ * SEO configuration extending Next.js Metadata with additional fields
13
+ *
14
+ * - `name`: Site name (used as fallback for openGraph.siteName)
15
+ * - `url`: Site URL (used as fallback for openGraph.url)
16
+ * - `jsonld`: JSON-LD structured data schemas
67
17
  */
68
- interface SEOConfig {
18
+ interface SEOConfig extends Metadata {
19
+ /** Site name, used as fallback for openGraph.siteName */
69
20
  name: string;
21
+ /** Site URL, used as fallback for openGraph.url */
70
22
  url: string;
71
- title?: TemplateString | string;
72
- description?: string;
73
- keywords?: string[];
74
- creator?: string;
75
- publisher?: string;
76
- authors?: SEOAuthor[];
77
- manifest?: string;
78
- icons?: SEOIcons;
79
- formatDetection?: SEOFormatDetection;
80
- openGraph?: SEOOpenGraph;
81
- twitter?: SEOTwitter;
82
- robots?: SEORobots;
23
+ /** JSON-LD structured data schemas */
83
24
  jsonld?: SEOJsonLd[];
84
- locale?: string;
85
25
  }
86
26
 
87
- export type { SEOAuthor, SEOConfig, SEOFormatDetection, SEOIcons, SEOImage, SEOJsonLd, SEOOpenGraph, SEORobots, SEOTwitter };
27
+ export type { SEOConfig, SEOJsonLd };
package/dist/next.cjs CHANGED
@@ -1 +1 @@
1
- 'use strict';var n=class{config;constructor(t){this.config=t;}configToMetadata(t){let o={...this.config,...t},i={title:o.title,description:o.description,keywords:o.keywords,creator:o.creator,publisher:o.publisher,authors:o.authors?.map(e=>({name:e.name,url:e.url})),manifest:o.manifest,icons:o.icons?{...this.config.icons,...t?.icons}:void 0,formatDetection:o.formatDetection?{...this.config.formatDetection,...t?.formatDetection}:void 0,openGraph:o.openGraph?{...this.config.openGraph,...t?.openGraph,url:t?.openGraph?.url??this.config.openGraph?.url??this.config.url,siteName:t?.openGraph?.siteName??this.config.openGraph?.siteName??this.config.name}:void 0,twitter:o.twitter?{...this.config.twitter,...t?.twitter}:void 0,robots:o.robots?{...this.config.robots,...t?.robots,googleBot:t?.robots?.googleBot||this.config.robots?.googleBot?{...this.config.robots?.googleBot,...t?.robots?.googleBot}:void 0}:void 0};return Object.fromEntries(Object.entries(i).filter(([e,a])=>a!==void 0))}generatePageMetadata(t){return this.configToMetadata(t)}};exports.Seox=n;
1
+ 'use strict';var r=require('react');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var r__namespace=/*#__PURE__*/_interopNamespace(r);var s=class{config;constructor(o){this.config=o;}configToMetadata(o){let{name:t,url:e,jsonld:p,...n}=this.config,{name:c,url:g,jsonld:m,...a}=o??{},d={...n,...a};return (n.openGraph||a.openGraph)&&(d.openGraph={...n.openGraph,...a.openGraph,url:a.openGraph?.url??n.openGraph?.url??e,siteName:a.openGraph?.siteName??n.openGraph?.siteName??t}),d}generatePageMetadata(o){return this.configToMetadata(o)}getJsonLd(o){let t=this.config.jsonld??[],e=o??[];return [...t,...e]}getJsonLdStrings(o){return this.getJsonLd(o).map(t=>JSON.stringify(t))}};function f({seo:i,additionalSchemas:o}){let t=i.getJsonLd(o);return !t||t.length===0?null:r__namespace.createElement(r__namespace.Fragment,null,t.map((e,p)=>r__namespace.createElement("script",{key:`jsonld-${p}`,type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e)}})))}exports.JsonLd=f;exports.Seox=s;
package/dist/next.d.cts CHANGED
@@ -1,13 +1,51 @@
1
- import { SEOConfig } from './index.cjs';
1
+ import { SEOConfig, SEOJsonLd } from './index.cjs';
2
2
  import { Metadata } from 'next';
3
- import 'next/dist/lib/metadata/types/metadata-types';
4
- import 'next/dist/lib/metadata/types/opengraph-types';
3
+ import * as React from 'react';
5
4
 
6
5
  declare class Seox {
7
- private config;
6
+ config: SEOConfig;
8
7
  constructor(config: SEOConfig);
9
8
  configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
10
9
  generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
10
+ /**
11
+ * Returns the JSON-LD schemas configured for this SEO config
12
+ * @param overrides - Optional additional JSON-LD schemas to include
13
+ * @returns Array of JSON-LD schema objects
14
+ */
15
+ getJsonLd(overrides?: SEOJsonLd[]): SEOJsonLd[];
16
+ /**
17
+ * Returns the JSON-LD schemas as stringified JSON strings ready for injection
18
+ * @param overrides - Optional additional JSON-LD schemas to include
19
+ * @returns Array of stringified JSON-LD schemas
20
+ */
21
+ getJsonLdStrings(overrides?: SEOJsonLd[]): string[];
11
22
  }
12
23
 
13
- export { SEOConfig, Seox };
24
+ interface JsonLdProps {
25
+ seo: Seox;
26
+ additionalSchemas?: SEOJsonLd[];
27
+ }
28
+ /**
29
+ * React component to inject JSON-LD structured data into the page
30
+ * Use this component in your layout.tsx inside the <head> tag
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { seoConfig } from '@/lib/seo';
35
+ * import { JsonLd } from 'seox/next';
36
+ *
37
+ * export default function RootLayout({ children }) {
38
+ * return (
39
+ * <html lang="fr">
40
+ * <head>
41
+ * <JsonLd seo={seoConfig} />
42
+ * </head>
43
+ * <body>{children}</body>
44
+ * </html>
45
+ * );
46
+ * }
47
+ * ```
48
+ */
49
+ declare function JsonLd({ seo, additionalSchemas }: JsonLdProps): React.JSX.Element | null;
50
+
51
+ export { JsonLd, SEOConfig, SEOJsonLd, Seox };
package/dist/next.d.ts CHANGED
@@ -1,13 +1,51 @@
1
- import { SEOConfig } from './index.js';
1
+ import { SEOConfig, SEOJsonLd } from './index.js';
2
2
  import { Metadata } from 'next';
3
- import 'next/dist/lib/metadata/types/metadata-types';
4
- import 'next/dist/lib/metadata/types/opengraph-types';
3
+ import * as React from 'react';
5
4
 
6
5
  declare class Seox {
7
- private config;
6
+ config: SEOConfig;
8
7
  constructor(config: SEOConfig);
9
8
  configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
10
9
  generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
10
+ /**
11
+ * Returns the JSON-LD schemas configured for this SEO config
12
+ * @param overrides - Optional additional JSON-LD schemas to include
13
+ * @returns Array of JSON-LD schema objects
14
+ */
15
+ getJsonLd(overrides?: SEOJsonLd[]): SEOJsonLd[];
16
+ /**
17
+ * Returns the JSON-LD schemas as stringified JSON strings ready for injection
18
+ * @param overrides - Optional additional JSON-LD schemas to include
19
+ * @returns Array of stringified JSON-LD schemas
20
+ */
21
+ getJsonLdStrings(overrides?: SEOJsonLd[]): string[];
11
22
  }
12
23
 
13
- export { SEOConfig, Seox };
24
+ interface JsonLdProps {
25
+ seo: Seox;
26
+ additionalSchemas?: SEOJsonLd[];
27
+ }
28
+ /**
29
+ * React component to inject JSON-LD structured data into the page
30
+ * Use this component in your layout.tsx inside the <head> tag
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { seoConfig } from '@/lib/seo';
35
+ * import { JsonLd } from 'seox/next';
36
+ *
37
+ * export default function RootLayout({ children }) {
38
+ * return (
39
+ * <html lang="fr">
40
+ * <head>
41
+ * <JsonLd seo={seoConfig} />
42
+ * </head>
43
+ * <body>{children}</body>
44
+ * </html>
45
+ * );
46
+ * }
47
+ * ```
48
+ */
49
+ declare function JsonLd({ seo, additionalSchemas }: JsonLdProps): React.JSX.Element | null;
50
+
51
+ export { JsonLd, SEOConfig, SEOJsonLd, Seox };
package/dist/next.js CHANGED
@@ -1 +1 @@
1
- var n=class{config;constructor(t){this.config=t;}configToMetadata(t){let o={...this.config,...t},i={title:o.title,description:o.description,keywords:o.keywords,creator:o.creator,publisher:o.publisher,authors:o.authors?.map(e=>({name:e.name,url:e.url})),manifest:o.manifest,icons:o.icons?{...this.config.icons,...t?.icons}:void 0,formatDetection:o.formatDetection?{...this.config.formatDetection,...t?.formatDetection}:void 0,openGraph:o.openGraph?{...this.config.openGraph,...t?.openGraph,url:t?.openGraph?.url??this.config.openGraph?.url??this.config.url,siteName:t?.openGraph?.siteName??this.config.openGraph?.siteName??this.config.name}:void 0,twitter:o.twitter?{...this.config.twitter,...t?.twitter}:void 0,robots:o.robots?{...this.config.robots,...t?.robots,googleBot:t?.robots?.googleBot||this.config.robots?.googleBot?{...this.config.robots?.googleBot,...t?.robots?.googleBot}:void 0}:void 0};return Object.fromEntries(Object.entries(i).filter(([e,a])=>a!==void 0))}generatePageMetadata(t){return this.configToMetadata(t)}};export{n as Seox};
1
+ import*as r from'react';var s=class{config;constructor(o){this.config=o;}configToMetadata(o){let{name:t,url:e,jsonld:p,...n}=this.config,{name:c,url:g,jsonld:m,...a}=o??{},d={...n,...a};return (n.openGraph||a.openGraph)&&(d.openGraph={...n.openGraph,...a.openGraph,url:a.openGraph?.url??n.openGraph?.url??e,siteName:a.openGraph?.siteName??n.openGraph?.siteName??t}),d}generatePageMetadata(o){return this.configToMetadata(o)}getJsonLd(o){let t=this.config.jsonld??[],e=o??[];return [...t,...e]}getJsonLdStrings(o){return this.getJsonLd(o).map(t=>JSON.stringify(t))}};function f({seo:i,additionalSchemas:o}){let t=i.getJsonLd(o);return !t||t.length===0?null:r.createElement(r.Fragment,null,t.map((e,p)=>r.createElement("script",{key:`jsonld-${p}`,type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e)}})))}export{f as JsonLd,s as Seox};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seox",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },