seox 1.2.0 → 1.3.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.
Files changed (3) hide show
  1. package/dist/cli.cjs +37 -37
  2. package/dist/cli.js +37 -37
  3. package/package.json +4 -1
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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";
2
+ 'use strict';var commander=require('commander'),w=require('path'),n=require('chalk'),u=require('fs-extra'),j=require('ora'),A=require('prompts');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var w__default=/*#__PURE__*/_interopDefault(w);var n__default=/*#__PURE__*/_interopDefault(n);var u__default=/*#__PURE__*/_interopDefault(u);var j__default=/*#__PURE__*/_interopDefault(j);var A__default=/*#__PURE__*/_interopDefault(A);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 N={config:`import { Seox } from "seox/next";
3
3
 
4
4
  export const seoConfig = new Seox({
5
5
  name: "{{siteName}}",
@@ -29,46 +29,46 @@ export const seoConfig = new Seox({
29
29
  // Add more fields: logo, address, contactPoint, sameAs...
30
30
  },
31
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>
32
+ })`};function g(t){return u__default.default.existsSync(`${process.cwd()}/src`)?w__default.default.join(process.cwd(),"src","lib",t):w__default.default.join(process.cwd(),"lib",t)}function M(t){return typeof t=="string"?t:t&&typeof t=="object"&&"default"in t?t.default:null}function b(t){let o={errors:[],warnings:[],suggestions:[]},s=M(t.title);return s?s.length>60&&o.warnings.push(`title is too long (${s.length} > 60 characters)`):o.errors.push("title is missing"),t.description?t.description.length>160&&o.warnings.push(`description is too long (${t.description.length} > 160 characters)`):o.errors.push("description is missing"),(!t.jsonld||t.jsonld.length===0)&&o.suggestions.push("add JSON-LD structured data to improve search engine indexing"),o}async function O(){let t=[],o=u__default.default.existsSync(`${process.cwd()}/src`)?"src":"",s=w__default.default.join(process.cwd(),o,"app"),e=w__default.default.join(process.cwd(),o,"pages");return await u__default.default.pathExists(s)&&await $(s,t),await u__default.default.pathExists(e)&&await $(e,t),t}async function $(t,o,s){let e=await u__default.default.readdir(t,{withFileTypes:true});for(let r of e){let a=w__default.default.join(t,r.name);if(r.isDirectory())await $(a,o);else if(r.isFile()&&(r.name==="layout.tsx"||r.name==="page.tsx"||r.name==="layout.ts"||r.name==="page.ts")){let i=await u__default.default.readFile(a,"utf8"),l=i.includes("export const metadata"),f=l?v(i):void 0;o.push({path:a,hasMetadata:l,metadataContent:f});}}}function v(t){let o=t.split(`
33
+ `),s=[],e=false,r=0;for(let a of o){if(a.includes("export const metadata")){e=true,s.push(a),r=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length;continue}if(e&&(s.push(a),r+=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length,r===0))break}return s.join(`
34
+ `)}function I(t){return t.endsWith("layout.tsx")||t.endsWith("layout.ts")}function L(t){if(t.includes("<JsonLd"))return t;let o=t;if(o.includes("import { seoConfig } from '@/lib/seo'")&&!o.includes("import { JsonLd }")){let s=o.split(`
35
+ `),e=0;for(let r=0;r<s.length;r++)if(s[r].includes("import { seoConfig } from '@/lib/seo'")){e=r+1;break}s.splice(e,0,"import { JsonLd } from 'seox/next';"),o=s.join(`
36
+ `);}return o.includes("<head>")&&!o.includes("<JsonLd")?o=o.replace(/<head>(\s*)/,`<head>$1<JsonLd seo={seoConfig} />
37
+ $1`):o.includes("<head")&&!o.includes("<JsonLd")?o=o.replace(/(<head[^>]*>)(\s*)/,`$1$2<JsonLd seo={seoConfig} />
38
+ $2`):!o.includes("<head")&&o.includes("<html")&&(o=o.replace(/(<html[^>]*>)(\s*)/,`$1$2<head>
39
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=`
40
+ </head>$2`)),o}async function x(t,o=false){let s=await u__default.default.readFile(t,"utf8");if(s.includes("export const metadata")&&!o)return false;let e=s;if(o&&s.includes("export const metadata")&&(e=s.replace(/export const metadata[^;]+;?\s*/gs,"")),!e.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
41
+ `,f=e.split(`
42
+ `),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),e=f.join(`
43
+ `);}let r=`
44
44
  export const metadata = seoConfig.configToMetadata();
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(`
45
+ `,a=e.split(`
46
+ `),i=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")){i=l;break}return a.splice(i,0,r),e=a.join(`
47
+ `),I(t)&&(e=L(e)),await u__default.default.writeFile(t,e,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async t=>{if(console.log(n__default.default.cyan.bold(`
48
48
  \u{1F9E0} ${m} - Initialization
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(`
49
+ `)),await u__default.default.pathExists(g(c))){let{overwrite:e}=await A__default.default({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!e){console.log(n__default.default.yellow("\u26A0\uFE0F Initialization canceled"));return}}let o=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:e=>e.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!o.siteName){console.log(n__default.default.red("\u274C Initialization canceled"));return}let s=j__default.default("Creating files...").start();try{await u__default.default.ensureDir(w__default.default.join(process.cwd(),"lib"));let e=N.config;Object.entries(o).forEach(([r,a])=>{e=e.replace(new RegExp(`{{${r}}}`,"g"),a);}),await u__default.default.writeFile(g(c),e,"utf8"),s.succeed("Configuration created successfully!"),console.log(n__default.default.green(`
50
+ \u2705 File created : `)+n__default.default.gray(g(c))),console.log(n__default.default.cyan(`
51
+ \u{1F4DD} Next step :`)),console.log(n__default.default.white(" bunx seox configure")),console.log();}catch(e){s.fail("Error creating the configuration"),console.error(n__default.default.red(e.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 t=>{console.log(n__default.default.cyan.bold(`
52
52
  \u2699\uFE0F ${m} - Configuration
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(`
53
+ `));let o=j__default.default("Reading configuration...").start();try{if(!await u__default.default.pathExists(g(c))){o.fail(`${c} file not found`),console.log(n__default.default.yellow(`
54
+ \u{1F4A1} Run first: `)+n__default.default.white("bunx seox init"));return}o.text="Scanning Next.js files...";let s=await O();if(s.length===0){o.fail("No Next.js layout or page files found"),console.log(n__default.default.yellow(`
55
+ \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}o.succeed(`Found ${s.length} file(s) to process`),console.log(n__default.default.cyan(`
56
+ \u{1F4C1} Files found:`)),s.forEach(i=>{let l=i.hasMetadata?n__default.default.yellow("(has metadata)"):n__default.default.green("(no metadata)");console.log(n__default.default.gray(` \u2022 ${i.path} ${l}`));});let e={added:0,overwritten:0,skipped:0,errors:[]},r=s.filter(i=>i.hasMetadata),a=s.filter(i=>!i.hasMetadata);console.log(n__default.default.cyan(`
57
+ \u{1F504} Processing files without metadata...`));for(let i of a)try{await x(i.path,!1)&&(e.added++,console.log(n__default.default.green(` \u2713 Added metadata to ${i.path}`)));}catch(l){e.errors.push(`${i.path}: ${l.message}`),console.log(n__default.default.red(` \u2717 Error in ${i.path}`));}if(r.length>0)if(console.log(n__default.default.yellow(`
58
+ \u26A0\uFE0F ${r.length} file(s) already have metadata exports`)),t.force){console.log(n__default.default.cyan(`
59
+ \u{1F504} Overwriting all existing metadata (--force)...`));for(let i of r)try{await x(i.path,!0)&&(e.overwritten++,console.log(n__default.default.yellow(` \u2713 Overwritten ${i.path}`)));}catch(l){e.errors.push(`${i.path}: ${l.message}`),console.log(n__default.default.red(` \u2717 Error in ${i.path}`));}}else {console.log(n__default.default.cyan(`
60
+ \u{1F504} Processing files with existing metadata...`));for(let i of r){let l=await A__default.default({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${i.path}?`,initial:!1});if(l.overwrite===void 0){console.log(n__default.default.yellow(`
61
+ \u274C Operation cancelled`));break}if(l.overwrite)try{await x(i.path,!0)&&(e.overwritten++,console.log(n__default.default.yellow(` \u2713 Overwritten ${i.path}`)));}catch(f){e.errors.push(`${i.path}: ${f.message}`),console.log(n__default.default.red(` \u2717 Error in ${i.path}`));}else e.skipped++,console.log(n__default.default.gray(` \u25CB Skipped ${i.path}`));}}console.log(n__default.default.green.bold(`
62
62
  \u2705 Configuration Summary:
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(`
63
+ `)),console.log(n__default.default.gray(` \u2022 ${e.added} file(s) with metadata added`)),e.overwritten>0&&console.log(n__default.default.yellow(` \u2022 ${e.overwritten} file(s) overwritten`)),e.skipped>0&&console.log(n__default.default.gray(` \u2022 ${e.skipped} file(s) skipped`)),e.errors.length>0&&(console.log(n__default.default.red(` \u2022 ${e.errors.length} error(s):`)),e.errors.forEach(i=>{console.log(n__default.default.red(` - ${i}`));})),(e.added>0||e.overwritten>0)&&(console.log(n__default.default.cyan.bold(`
64
64
  \u{1F4DD} Next Steps:
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(`
65
+ `)),console.log(n__default.default.white(" The following has been added to your files:")),console.log(n__default.default.gray(" import { seoConfig } from '@/lib/seo';")),console.log(n__default.default.gray(" import { JsonLd } from 'seox/next';")),console.log(n__default.default.gray(` export const metadata = seoConfig.configToMetadata();
66
+ `)),console.log(n__default.default.white(" For layout files, the <JsonLd /> component was also added:")),console.log(n__default.default.gray(" <head>")),console.log(n__default.default.gray(" <JsonLd seo={seoConfig} />")),console.log(n__default.default.gray(` </head>
67
+ `)),console.log(n__default.default.white(" To customize metadata for specific pages, you can:")),console.log(n__default.default.gray(" 1. Pass overrides to configToMetadata():")),console.log(n__default.default.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(n__default.default.gray(' title: "Custom Page Title",')),console.log(n__default.default.gray(' description: "Custom description"')),console.log(n__default.default.gray(` });
68
+ `)),console.log(n__default.default.gray(" 2. Add JSON-LD schemas in your seo.ts config")),console.log());}catch(s){o.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(n__default.default.red(s.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(n__default.default.cyan.bold(`
69
69
  \u{1FA7A} ${m} - SEO Diagnostic
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;
70
+ `));let t=j__default.default("Analyzing...").start();try{if(!await u__default.default.pathExists(g(c))){t.fail(`${c} file not found in ${g(c)}`);return}let s=(await import(g(c))).seoConfig;if(!s||!s.config){t.fail('seoConfig not found in configuration file. Make sure you export "seoConfig".');return}t.stop();let e=b(s.config);if(e.errors.length===0&&e.warnings.length===0){console.log(n__default.default.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
71
+ `));return}e.errors.length>0&&(console.log(n__default.default.red.bold(`\u274C ERRORS :
72
+ `)),e.errors.map(r=>console.log(n__default.default.red(` \u2022 ${r}`))),console.log()),e.warnings.length>0&&(console.log(n__default.default.yellow.bold(`\u26A0\uFE0F WARNINGS :
73
+ `)),e.warnings.map(r=>console.log(n__default.default.yellow(` \u2022 ${r}`))),console.log()),e.suggestions.length>0&&(console.log(n__default.default.cyan.bold(`\u{1F4A1} SUGGESTIONS :
74
+ `)),e.suggestions.map(r=>console.log(n__default.default.cyan(` \u2022 ${r}`))),console.log());}catch(o){t.fail("Error during diagnosis"),console.error(n__default.default.red(o.message));}});p.parse(process.argv);var $e=p;module.exports=$e;
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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";
2
+ import {Command}from'commander';import w from'path';import n from'chalk';import u from'fs-extra';import j from'ora';import A 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 N={config:`import { Seox } from "seox/next";
3
3
 
4
4
  export const seoConfig = new Seox({
5
5
  name: "{{siteName}}",
@@ -29,46 +29,46 @@ export const seoConfig = new Seox({
29
29
  // Add more fields: logo, address, contactPoint, sameAs...
30
30
  },
31
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>
32
+ })`};function g(t){return u.existsSync(`${process.cwd()}/src`)?w.join(process.cwd(),"src","lib",t):w.join(process.cwd(),"lib",t)}function M(t){return typeof t=="string"?t:t&&typeof t=="object"&&"default"in t?t.default:null}function b(t){let o={errors:[],warnings:[],suggestions:[]},s=M(t.title);return s?s.length>60&&o.warnings.push(`title is too long (${s.length} > 60 characters)`):o.errors.push("title is missing"),t.description?t.description.length>160&&o.warnings.push(`description is too long (${t.description.length} > 160 characters)`):o.errors.push("description is missing"),(!t.jsonld||t.jsonld.length===0)&&o.suggestions.push("add JSON-LD structured data to improve search engine indexing"),o}async function O(){let t=[],o=u.existsSync(`${process.cwd()}/src`)?"src":"",s=w.join(process.cwd(),o,"app"),e=w.join(process.cwd(),o,"pages");return await u.pathExists(s)&&await $(s,t),await u.pathExists(e)&&await $(e,t),t}async function $(t,o,s){let e=await u.readdir(t,{withFileTypes:true});for(let r of e){let a=w.join(t,r.name);if(r.isDirectory())await $(a,o);else if(r.isFile()&&(r.name==="layout.tsx"||r.name==="page.tsx"||r.name==="layout.ts"||r.name==="page.ts")){let i=await u.readFile(a,"utf8"),l=i.includes("export const metadata"),f=l?v(i):void 0;o.push({path:a,hasMetadata:l,metadataContent:f});}}}function v(t){let o=t.split(`
33
+ `),s=[],e=false,r=0;for(let a of o){if(a.includes("export const metadata")){e=true,s.push(a),r=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length;continue}if(e&&(s.push(a),r+=(a.match(/\{/g)||[]).length-(a.match(/\}/g)||[]).length,r===0))break}return s.join(`
34
+ `)}function I(t){return t.endsWith("layout.tsx")||t.endsWith("layout.ts")}function L(t){if(t.includes("<JsonLd"))return t;let o=t;if(o.includes("import { seoConfig } from '@/lib/seo'")&&!o.includes("import { JsonLd }")){let s=o.split(`
35
+ `),e=0;for(let r=0;r<s.length;r++)if(s[r].includes("import { seoConfig } from '@/lib/seo'")){e=r+1;break}s.splice(e,0,"import { JsonLd } from 'seox/next';"),o=s.join(`
36
+ `);}return o.includes("<head>")&&!o.includes("<JsonLd")?o=o.replace(/<head>(\s*)/,`<head>$1<JsonLd seo={seoConfig} />
37
+ $1`):o.includes("<head")&&!o.includes("<JsonLd")?o=o.replace(/(<head[^>]*>)(\s*)/,`$1$2<JsonLd seo={seoConfig} />
38
+ $2`):!o.includes("<head")&&o.includes("<html")&&(o=o.replace(/(<html[^>]*>)(\s*)/,`$1$2<head>
39
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=`
40
+ </head>$2`)),o}async function x(t,o=false){let s=await u.readFile(t,"utf8");if(s.includes("export const metadata")&&!o)return false;let e=s;if(o&&s.includes("export const metadata")&&(e=s.replace(/export const metadata[^;]+;?\s*/gs,"")),!e.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
41
+ `,f=e.split(`
42
+ `),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),e=f.join(`
43
+ `);}let r=`
44
44
  export const metadata = seoConfig.configToMetadata();
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(`
45
+ `,a=e.split(`
46
+ `),i=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")){i=l;break}return a.splice(i,0,r),e=a.join(`
47
+ `),I(t)&&(e=L(e)),await u.writeFile(t,e,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async t=>{if(console.log(n.cyan.bold(`
48
48
  \u{1F9E0} ${m} - Initialization
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(`
49
+ `)),await u.pathExists(g(c))){let{overwrite:e}=await A({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!e){console.log(n.yellow("\u26A0\uFE0F Initialization canceled"));return}}let o=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:e=>e.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!o.siteName){console.log(n.red("\u274C Initialization canceled"));return}let s=j("Creating files...").start();try{await u.ensureDir(w.join(process.cwd(),"lib"));let e=N.config;Object.entries(o).forEach(([r,a])=>{e=e.replace(new RegExp(`{{${r}}}`,"g"),a);}),await u.writeFile(g(c),e,"utf8"),s.succeed("Configuration created successfully!"),console.log(n.green(`
50
+ \u2705 File created : `)+n.gray(g(c))),console.log(n.cyan(`
51
+ \u{1F4DD} Next step :`)),console.log(n.white(" bunx seox configure")),console.log();}catch(e){s.fail("Error creating the configuration"),console.error(n.red(e.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 t=>{console.log(n.cyan.bold(`
52
52
  \u2699\uFE0F ${m} - Configuration
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(`
53
+ `));let o=j("Reading configuration...").start();try{if(!await u.pathExists(g(c))){o.fail(`${c} file not found`),console.log(n.yellow(`
54
+ \u{1F4A1} Run first: `)+n.white("bunx seox init"));return}o.text="Scanning Next.js files...";let s=await O();if(s.length===0){o.fail("No Next.js layout or page files found"),console.log(n.yellow(`
55
+ \u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}o.succeed(`Found ${s.length} file(s) to process`),console.log(n.cyan(`
56
+ \u{1F4C1} Files found:`)),s.forEach(i=>{let l=i.hasMetadata?n.yellow("(has metadata)"):n.green("(no metadata)");console.log(n.gray(` \u2022 ${i.path} ${l}`));});let e={added:0,overwritten:0,skipped:0,errors:[]},r=s.filter(i=>i.hasMetadata),a=s.filter(i=>!i.hasMetadata);console.log(n.cyan(`
57
+ \u{1F504} Processing files without metadata...`));for(let i of a)try{await x(i.path,!1)&&(e.added++,console.log(n.green(` \u2713 Added metadata to ${i.path}`)));}catch(l){e.errors.push(`${i.path}: ${l.message}`),console.log(n.red(` \u2717 Error in ${i.path}`));}if(r.length>0)if(console.log(n.yellow(`
58
+ \u26A0\uFE0F ${r.length} file(s) already have metadata exports`)),t.force){console.log(n.cyan(`
59
+ \u{1F504} Overwriting all existing metadata (--force)...`));for(let i of r)try{await x(i.path,!0)&&(e.overwritten++,console.log(n.yellow(` \u2713 Overwritten ${i.path}`)));}catch(l){e.errors.push(`${i.path}: ${l.message}`),console.log(n.red(` \u2717 Error in ${i.path}`));}}else {console.log(n.cyan(`
60
+ \u{1F504} Processing files with existing metadata...`));for(let i of r){let l=await A({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${i.path}?`,initial:!1});if(l.overwrite===void 0){console.log(n.yellow(`
61
+ \u274C Operation cancelled`));break}if(l.overwrite)try{await x(i.path,!0)&&(e.overwritten++,console.log(n.yellow(` \u2713 Overwritten ${i.path}`)));}catch(f){e.errors.push(`${i.path}: ${f.message}`),console.log(n.red(` \u2717 Error in ${i.path}`));}else e.skipped++,console.log(n.gray(` \u25CB Skipped ${i.path}`));}}console.log(n.green.bold(`
62
62
  \u2705 Configuration Summary:
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(`
63
+ `)),console.log(n.gray(` \u2022 ${e.added} file(s) with metadata added`)),e.overwritten>0&&console.log(n.yellow(` \u2022 ${e.overwritten} file(s) overwritten`)),e.skipped>0&&console.log(n.gray(` \u2022 ${e.skipped} file(s) skipped`)),e.errors.length>0&&(console.log(n.red(` \u2022 ${e.errors.length} error(s):`)),e.errors.forEach(i=>{console.log(n.red(` - ${i}`));})),(e.added>0||e.overwritten>0)&&(console.log(n.cyan.bold(`
64
64
  \u{1F4DD} Next Steps:
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(`
65
+ `)),console.log(n.white(" The following has been added to your files:")),console.log(n.gray(" import { seoConfig } from '@/lib/seo';")),console.log(n.gray(" import { JsonLd } from 'seox/next';")),console.log(n.gray(` export const metadata = seoConfig.configToMetadata();
66
+ `)),console.log(n.white(" For layout files, the <JsonLd /> component was also added:")),console.log(n.gray(" <head>")),console.log(n.gray(" <JsonLd seo={seoConfig} />")),console.log(n.gray(` </head>
67
+ `)),console.log(n.white(" To customize metadata for specific pages, you can:")),console.log(n.gray(" 1. Pass overrides to configToMetadata():")),console.log(n.gray(" export const metadata = seoConfig.configToMetadata({")),console.log(n.gray(' title: "Custom Page Title",')),console.log(n.gray(' description: "Custom description"')),console.log(n.gray(` });
68
+ `)),console.log(n.gray(" 2. Add JSON-LD schemas in your seo.ts config")),console.log());}catch(s){o.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(n.red(s.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(n.cyan.bold(`
69
69
  \u{1FA7A} ${m} - SEO Diagnostic
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};
70
+ `));let t=j("Analyzing...").start();try{if(!await u.pathExists(g(c))){t.fail(`${c} file not found in ${g(c)}`);return}let s=(await import(g(c))).seoConfig;if(!s||!s.config){t.fail('seoConfig not found in configuration file. Make sure you export "seoConfig".');return}t.stop();let e=b(s.config);if(e.errors.length===0&&e.warnings.length===0){console.log(n.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
71
+ `));return}e.errors.length>0&&(console.log(n.red.bold(`\u274C ERRORS :
72
+ `)),e.errors.map(r=>console.log(n.red(` \u2022 ${r}`))),console.log()),e.warnings.length>0&&(console.log(n.yellow.bold(`\u26A0\uFE0F WARNINGS :
73
+ `)),e.warnings.map(r=>console.log(n.yellow(` \u2022 ${r}`))),console.log()),e.suggestions.length>0&&(console.log(n.cyan.bold(`\u{1F4A1} SUGGESTIONS :
74
+ `)),e.suggestions.map(r=>console.log(n.cyan(` \u2022 ${r}`))),console.log());}catch(o){t.fail("Error during diagnosis"),console.error(n.red(o.message));}});p.parse(process.argv);var $e=p;export{$e as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seox",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -52,7 +52,10 @@
52
52
  ],
53
53
  "devDependencies": {
54
54
  "@biomejs/biome": "2.2.4",
55
+ "@testing-library/dom": "^10.4.1",
56
+ "@testing-library/react": "^16.3.2",
55
57
  "@types/bun": "latest",
58
+ "happy-dom": "^20.3.7",
56
59
  "husky": "^9.1.7"
57
60
  },
58
61
  "dependencies": {