seox 0.0.1 → 0.1.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 +32 -32
- package/dist/cli.cjs +35 -35
- package/dist/cli.js +35 -35
- package/dist/next.cjs +1 -1
- package/dist/next.d.cts +2 -2
- package/dist/next.d.ts +2 -2
- package/dist/next.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# 🧠
|
|
1
|
+
# 🧠 Seox
|
|
2
2
|
|
|
3
3
|
> **Simplified SEO management for Next.js App Router**
|
|
4
4
|
|
|
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
8
|
[](https://www.npmjs.com/package/@neysixx/metanext)
|
|
@@ -16,7 +16,7 @@ It combines **TypeScript-typed configuration**, **automatic metadata injection**
|
|
|
16
16
|
In a typical Next.js project, each page manually repeats its `<Head>` tags, metadata, and JSON-LD.
|
|
17
17
|
👉 This creates **duplication**, **inconsistencies**, and complicates maintenance.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Seox provides:
|
|
20
20
|
- **TypeScript-typed configuration** (`lib/seo.ts`)
|
|
21
21
|
- **Automatic metadata injection** into your Next.js files
|
|
22
22
|
- **Simple API**: `seoConfig.configToMetadata()`
|
|
@@ -30,23 +30,23 @@ MetaNext provides:
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
# Using Bun (recommended)
|
|
33
|
-
bun i
|
|
33
|
+
bun i seox
|
|
34
34
|
|
|
35
35
|
# Using npm
|
|
36
|
-
npm i
|
|
36
|
+
npm i seox
|
|
37
37
|
|
|
38
38
|
# Using pnpm
|
|
39
|
-
pnpm i
|
|
39
|
+
pnpm i seox
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
### 2. Initialize Configuration
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
# Using Bun (recommended)
|
|
46
|
-
bunx
|
|
46
|
+
bunx seox init
|
|
47
47
|
|
|
48
48
|
# Using npx
|
|
49
|
-
npx
|
|
49
|
+
npx seox init
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
This creates a `lib/seo.ts` file with interactive setup.
|
|
@@ -55,14 +55,14 @@ This creates a `lib/seo.ts` file with interactive setup.
|
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
# Scan and inject metadata into your Next.js files
|
|
58
|
-
bunx
|
|
58
|
+
bunx seox configure
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
### 4. Verify Configuration (In progress)
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
# Check your SEO configuration
|
|
65
|
-
bunx
|
|
65
|
+
bunx seox doctor
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
---
|
|
@@ -74,9 +74,9 @@ bunx metanext doctor
|
|
|
74
74
|
Created via the `init` command:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import {
|
|
77
|
+
import { Seox } from "seox/next";
|
|
78
78
|
|
|
79
|
-
export const seoConfig = new
|
|
79
|
+
export const seoConfig = new Seox({
|
|
80
80
|
name: "My Awesome Site",
|
|
81
81
|
url: "https://mysite.com",
|
|
82
82
|
title: {
|
|
@@ -114,7 +114,7 @@ Configuration is **centralized** and **reusable**.
|
|
|
114
114
|
Once the file is completed:
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
bunx
|
|
117
|
+
bunx seox configure
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
This command:
|
|
@@ -125,7 +125,7 @@ This command:
|
|
|
125
125
|
|
|
126
126
|
### 3. Usage in Your Pages
|
|
127
127
|
|
|
128
|
-
After running `bunx
|
|
128
|
+
After running `bunx seox configure`, your files are automatically updated:
|
|
129
129
|
|
|
130
130
|
```tsx
|
|
131
131
|
// app/layout.tsx (automatically generated)
|
|
@@ -185,28 +185,28 @@ export const metadata = seoConfig.configToMetadata({
|
|
|
185
185
|
});
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
Seox merges these fields with the global configuration.
|
|
189
189
|
|
|
190
190
|
---
|
|
191
191
|
|
|
192
192
|
## 🧰 Built-in CLI
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
Seox provides an intuitive CLI:
|
|
195
195
|
|
|
196
196
|
| Command | Description |
|
|
197
197
|
|---------|-------------|
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
198
|
+
| `seox init` | Creates `lib/seo.ts` with interactive setup |
|
|
199
|
+
| `seox configure` | Scans and injects metadata into your Next.js files |
|
|
200
|
+
| `seox doctor` (soon) | Validates your SEO configuration |
|
|
201
201
|
|
|
202
202
|
### Advanced Options
|
|
203
203
|
|
|
204
204
|
```bash
|
|
205
205
|
# Force overwrite existing metadata
|
|
206
|
-
bunx
|
|
206
|
+
bunx seox configure --force
|
|
207
207
|
|
|
208
208
|
# Validation only (no generation)
|
|
209
|
-
bunx
|
|
209
|
+
bunx seox configure --validate
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
---
|
|
@@ -233,7 +233,7 @@ bunx metanext configure --validate
|
|
|
233
233
|
|
|
234
234
|
## 📘 API & Helpers
|
|
235
235
|
|
|
236
|
-
### `
|
|
236
|
+
### `Seox` (main class)
|
|
237
237
|
Centralized SEO configuration with complete TypeScript typing.
|
|
238
238
|
|
|
239
239
|
### `configToMetadata(overrides?)`
|
|
@@ -276,16 +276,16 @@ Direct usage in your Next.js files to generate metadata.
|
|
|
276
276
|
|
|
277
277
|
```bash
|
|
278
278
|
# Bun (recommended)
|
|
279
|
-
bun add
|
|
279
|
+
bun add seox
|
|
280
280
|
|
|
281
281
|
# npm
|
|
282
|
-
npm install
|
|
282
|
+
npm install seox
|
|
283
283
|
|
|
284
284
|
# pnpm
|
|
285
|
-
pnpm add
|
|
285
|
+
pnpm add seox
|
|
286
286
|
|
|
287
287
|
# yarn
|
|
288
|
-
yarn add
|
|
288
|
+
yarn add seox
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
---
|
|
@@ -298,8 +298,8 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
|
298
298
|
|
|
299
299
|
```bash
|
|
300
300
|
# Clone the repository
|
|
301
|
-
git clone https://github.com/neysixx/
|
|
302
|
-
cd
|
|
301
|
+
git clone https://github.com/neysixx/seox.git
|
|
302
|
+
cd seox
|
|
303
303
|
|
|
304
304
|
# Install dependencies
|
|
305
305
|
bun install
|
|
@@ -327,9 +327,9 @@ MIT © 2025 — Designed for modern Next.js developers 🧑💻
|
|
|
327
327
|
|
|
328
328
|
## 📞 Support
|
|
329
329
|
|
|
330
|
-
- 📖 [Documentation](https://github.com/neysixx/
|
|
331
|
-
- 🐛 [Report Issues](https://github.com/neysixx/
|
|
332
|
-
- 💬 [Discussions](https://github.com/neysixx/
|
|
330
|
+
- 📖 [Documentation](https://github.com/neysixx/seox#readme)
|
|
331
|
+
- 🐛 [Report Issues](https://github.com/neysixx/seox/issues)
|
|
332
|
+
- 💬 [Discussions](https://github.com/neysixx/seox/discussions)
|
|
333
333
|
- 📧 [Email](mailto:kylliansenrens3004@gmail.com)
|
|
334
334
|
|
|
335
335
|
---
|
|
@@ -338,6 +338,6 @@ MIT © 2025 — Designed for modern Next.js developers 🧑💻
|
|
|
338
338
|
|
|
339
339
|
**Made with ❤️ for the Next.js community**
|
|
340
340
|
|
|
341
|
-
[⭐ Star us on GitHub](https://github.com/neysixx/
|
|
341
|
+
[⭐ Star us on GitHub](https://github.com/neysixx/seox) • [🐦 Follow on X](https://x.com/ks_nsx) • [📧 Contact](mailto:kylliansenrens3004@gmail.com)
|
|
342
342
|
|
|
343
343
|
</div>
|
package/dist/cli.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var commander=require('commander'),
|
|
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";
|
|
3
3
|
|
|
4
|
-
export const seoConfig = new
|
|
4
|
+
export const seoConfig = new Seox({
|
|
5
5
|
name: "{{siteName}}",
|
|
6
6
|
url: "{{baseUrl}}",
|
|
7
7
|
title: {
|
|
@@ -19,39 +19,39 @@ export const seoConfig = new MetaNext({
|
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
manifest: "",
|
|
22
|
-
})`};function g(a){return
|
|
23
|
-
`),
|
|
24
|
-
`)}async function
|
|
25
|
-
`,f=
|
|
26
|
-
`),
|
|
27
|
-
`);}let
|
|
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=`
|
|
28
28
|
export const metadata = seoConfig.configToMetadata();
|
|
29
|
-
`,i=
|
|
30
|
-
`),
|
|
31
|
-
`),await
|
|
32
|
-
\u{1F9E0}
|
|
33
|
-
`)),await
|
|
34
|
-
\u2705 File created : `)+
|
|
35
|
-
\u{1F4DD} Next step :`)),console.log(
|
|
36
|
-
\u2699\uFE0F ${
|
|
37
|
-
`));let
|
|
38
|
-
\u{1F4A1} Run first: `)+
|
|
39
|
-
\u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}
|
|
40
|
-
\u{1F4C1} Files found:`)),
|
|
41
|
-
\u{1F504} Processing files without metadata...`));for(let
|
|
42
|
-
\u26A0\uFE0F ${
|
|
43
|
-
\u{1F504} Overwriting all existing metadata (--force)...`));for(let
|
|
44
|
-
\u{1F504} Processing files with existing metadata...`));for(let
|
|
45
|
-
\u274C Operation cancelled`));break}if(l.overwrite)try{await
|
|
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(`
|
|
32
|
+
\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(`
|
|
36
|
+
\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(`
|
|
46
46
|
\u2705 Configuration Summary:
|
|
47
|
-
`)),console.log(
|
|
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(`
|
|
48
48
|
\u{1F4DD} Next Steps:
|
|
49
|
-
`)),console.log(
|
|
50
|
-
`)),console.log(
|
|
51
|
-
`)),console.log(
|
|
52
|
-
\u{1FA7A}
|
|
53
|
-
`));let a=
|
|
54
|
-
`));return}
|
|
55
|
-
`)),
|
|
56
|
-
`)),
|
|
57
|
-
`)),
|
|
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(`
|
|
52
|
+
\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;
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import
|
|
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";
|
|
3
3
|
|
|
4
|
-
export const seoConfig = new
|
|
4
|
+
export const seoConfig = new Seox({
|
|
5
5
|
name: "{{siteName}}",
|
|
6
6
|
url: "{{baseUrl}}",
|
|
7
7
|
title: {
|
|
@@ -19,39 +19,39 @@ export const seoConfig = new MetaNext({
|
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
manifest: "",
|
|
22
|
-
})`};function g(a){return
|
|
23
|
-
`),
|
|
24
|
-
`)}async function
|
|
25
|
-
`,f=
|
|
26
|
-
`),
|
|
27
|
-
`);}let
|
|
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=`
|
|
28
28
|
export const metadata = seoConfig.configToMetadata();
|
|
29
|
-
`,i=
|
|
30
|
-
`),
|
|
31
|
-
`),await
|
|
32
|
-
\u{1F9E0}
|
|
33
|
-
`)),await
|
|
34
|
-
\u2705 File created : `)+
|
|
35
|
-
\u{1F4DD} Next step :`)),console.log(
|
|
36
|
-
\u2699\uFE0F ${
|
|
37
|
-
`));let
|
|
38
|
-
\u{1F4A1} Run first: `)+
|
|
39
|
-
\u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}
|
|
40
|
-
\u{1F4C1} Files found:`)),
|
|
41
|
-
\u{1F504} Processing files without metadata...`));for(let
|
|
42
|
-
\u26A0\uFE0F ${
|
|
43
|
-
\u{1F504} Overwriting all existing metadata (--force)...`));for(let
|
|
44
|
-
\u{1F504} Processing files with existing metadata...`));for(let
|
|
45
|
-
\u274C Operation cancelled`));break}if(l.overwrite)try{await
|
|
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(`
|
|
32
|
+
\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(`
|
|
36
|
+
\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(`
|
|
46
46
|
\u2705 Configuration Summary:
|
|
47
|
-
`)),console.log(
|
|
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(`
|
|
48
48
|
\u{1F4DD} Next Steps:
|
|
49
|
-
`)),console.log(
|
|
50
|
-
`)),console.log(
|
|
51
|
-
`)),console.log(
|
|
52
|
-
\u{1FA7A}
|
|
53
|
-
`));let a=
|
|
54
|
-
`));return}
|
|
55
|
-
`)),
|
|
56
|
-
`)),
|
|
57
|
-
`)),
|
|
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(`
|
|
52
|
+
\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};
|
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.
|
|
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;
|
package/dist/next.d.cts
CHANGED
|
@@ -3,11 +3,11 @@ import { Metadata } from 'next';
|
|
|
3
3
|
import 'next/dist/lib/metadata/types/metadata-types';
|
|
4
4
|
import 'next/dist/lib/metadata/types/opengraph-types';
|
|
5
5
|
|
|
6
|
-
declare class
|
|
6
|
+
declare class Seox {
|
|
7
7
|
private config;
|
|
8
8
|
constructor(config: SEOConfig);
|
|
9
9
|
configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
10
10
|
generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export {
|
|
13
|
+
export { SEOConfig, Seox };
|
package/dist/next.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { Metadata } from 'next';
|
|
|
3
3
|
import 'next/dist/lib/metadata/types/metadata-types';
|
|
4
4
|
import 'next/dist/lib/metadata/types/opengraph-types';
|
|
5
5
|
|
|
6
|
-
declare class
|
|
6
|
+
declare class Seox {
|
|
7
7
|
private config;
|
|
8
8
|
constructor(config: SEOConfig);
|
|
9
9
|
configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
10
10
|
generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export {
|
|
13
|
+
export { SEOConfig, 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
|
|
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};
|