seox 0.0.1
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 +343 -0
- package/dist/cli.cjs +57 -0
- package/dist/cli.d.cts +6 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +57 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +0 -0
- package/dist/next.cjs +1 -0
- package/dist/next.d.cts +13 -0
- package/dist/next.d.ts +13 -0
- package/dist/next.js +1 -0
- package/package.json +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# 🧠 MetaNext
|
|
2
|
+
|
|
3
|
+
> **Simplified SEO management for Next.js App Router**
|
|
4
|
+
|
|
5
|
+
MetaNext 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
|
+
It combines **TypeScript-typed configuration**, **automatic metadata injection**, and an **intuitive CLI** to guide developers.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@neysixx/metanext)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🎯 Project Goal
|
|
15
|
+
|
|
16
|
+
In a typical Next.js project, each page manually repeats its `<Head>` tags, metadata, and JSON-LD.
|
|
17
|
+
👉 This creates **duplication**, **inconsistencies**, and complicates maintenance.
|
|
18
|
+
|
|
19
|
+
MetaNext provides:
|
|
20
|
+
- **TypeScript-typed configuration** (`lib/seo.ts`)
|
|
21
|
+
- **Automatic metadata injection** into your Next.js files
|
|
22
|
+
- **Simple API**: `seoConfig.configToMetadata()`
|
|
23
|
+
- **Complete CLI** to initialize and configure your project
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Using Bun (recommended)
|
|
33
|
+
bun i metanext
|
|
34
|
+
|
|
35
|
+
# Using npm
|
|
36
|
+
npm i metanext
|
|
37
|
+
|
|
38
|
+
# Using pnpm
|
|
39
|
+
pnpm i metanext
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Initialize Configuration
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Using Bun (recommended)
|
|
46
|
+
bunx metanext init
|
|
47
|
+
|
|
48
|
+
# Using npx
|
|
49
|
+
npx metanext init
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This creates a `lib/seo.ts` file with interactive setup.
|
|
53
|
+
|
|
54
|
+
### 3. Configure Your Project
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Scan and inject metadata into your Next.js files
|
|
58
|
+
bunx metanext configure
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 4. Verify Configuration (In progress)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Check your SEO configuration
|
|
65
|
+
bunx metanext doctor
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## ⚙️ How It Works
|
|
71
|
+
|
|
72
|
+
### 1. Configuration File: `lib/seo.ts`
|
|
73
|
+
|
|
74
|
+
Created via the `init` command:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { MetaNext } from "metanext/next";
|
|
78
|
+
|
|
79
|
+
export const seoConfig = new MetaNext({
|
|
80
|
+
name: "My Awesome Site",
|
|
81
|
+
url: "https://mysite.com",
|
|
82
|
+
title: {
|
|
83
|
+
default: "My Awesome Site",
|
|
84
|
+
template: "%s | My Awesome Site",
|
|
85
|
+
},
|
|
86
|
+
description: "Welcome to my modern Next.js site",
|
|
87
|
+
keywords: ["nextjs", "seo", "typescript"],
|
|
88
|
+
creator: "Your Name",
|
|
89
|
+
publisher: "Your Company",
|
|
90
|
+
authors: [
|
|
91
|
+
{
|
|
92
|
+
name: "Your Name",
|
|
93
|
+
url: "https://mysite.com/about",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
openGraph: {
|
|
97
|
+
type: "website",
|
|
98
|
+
locale: "en_US",
|
|
99
|
+
url: "https://mysite.com",
|
|
100
|
+
siteName: "My Awesome Site",
|
|
101
|
+
},
|
|
102
|
+
twitter: {
|
|
103
|
+
card: "summary_large_image",
|
|
104
|
+
creator: "@yourhandle",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
🧠 TypeScript typing guides you through field completion.
|
|
110
|
+
Configuration is **centralized** and **reusable**.
|
|
111
|
+
|
|
112
|
+
### 2. Automatic Configuration & Injection
|
|
113
|
+
|
|
114
|
+
Once the file is completed:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bunx metanext configure
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This command:
|
|
121
|
+
- **Scans** your Next.js files (`app/` and `pages/`)
|
|
122
|
+
- **Injects** metadata into your `layout.tsx` and `page.tsx`
|
|
123
|
+
- **Handles** conflicts with existing metadata
|
|
124
|
+
- **Validates** SEO consistency of your configuration
|
|
125
|
+
|
|
126
|
+
### 3. Usage in Your Pages
|
|
127
|
+
|
|
128
|
+
After running `bunx metanext configure`, your files are automatically updated:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
// app/layout.tsx (automatically generated)
|
|
132
|
+
import { seoConfig } from "@/lib/seo";
|
|
133
|
+
|
|
134
|
+
export const metadata = seoConfig.configToMetadata();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Page-specific Customization
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// app/page.tsx
|
|
141
|
+
import { seoConfig } from "@/lib/seo";
|
|
142
|
+
|
|
143
|
+
export const metadata = seoConfig.configToMetadata({
|
|
144
|
+
title: "Home | My Awesome Site",
|
|
145
|
+
description: "Welcome to our homepage",
|
|
146
|
+
openGraph: {
|
|
147
|
+
title: "Home - My Awesome Site",
|
|
148
|
+
description: "Discover our modern website",
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
💡 **Why this approach?**
|
|
154
|
+
- ✅ **Compatible** with all environments (AWS Amplify, Vercel, etc.)
|
|
155
|
+
- ✅ **Predictable** and explicit
|
|
156
|
+
- ✅ **Performant** (direct injection into Next.js metadata)
|
|
157
|
+
- ✅ **Type-safe** with TypeScript
|
|
158
|
+
- ✅ **Automatic** (no need to manually manage each page)
|
|
159
|
+
|
|
160
|
+
💡 The `configToMetadata()` method:
|
|
161
|
+
- Automatically generates:
|
|
162
|
+
- `<title>` and title templates
|
|
163
|
+
- `<meta name="description">`
|
|
164
|
+
- **OpenGraph** tags
|
|
165
|
+
- **Twitter Card** tags
|
|
166
|
+
- **robots** metadata
|
|
167
|
+
- **JSON-LD** (if configured)
|
|
168
|
+
- Server-side rendering (SSR/SSG) for optimal performance
|
|
169
|
+
|
|
170
|
+
### 4. Local Overrides
|
|
171
|
+
|
|
172
|
+
Need to modify certain values on the fly?
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// app/page.tsx
|
|
176
|
+
import { seoConfig } from "@/lib/seo";
|
|
177
|
+
|
|
178
|
+
export const metadata = seoConfig.configToMetadata({
|
|
179
|
+
title: "Home | Promo 2025",
|
|
180
|
+
description: "New offer available!",
|
|
181
|
+
openGraph: {
|
|
182
|
+
title: "Promo 2025 - My Awesome Site",
|
|
183
|
+
description: "Discover our exceptional offers",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
MetaNext merges these fields with the global configuration.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🧰 Built-in CLI
|
|
193
|
+
|
|
194
|
+
MetaNext provides an intuitive CLI:
|
|
195
|
+
|
|
196
|
+
| Command | Description |
|
|
197
|
+
|---------|-------------|
|
|
198
|
+
| `metanext init` | Creates `lib/seo.ts` with interactive setup |
|
|
199
|
+
| `metanext configure` | Scans and injects metadata into your Next.js files |
|
|
200
|
+
| `metanext doctor` (soon) | Validates your SEO configuration |
|
|
201
|
+
|
|
202
|
+
### Advanced Options
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Force overwrite existing metadata
|
|
206
|
+
bunx metanext configure --force
|
|
207
|
+
|
|
208
|
+
# Validation only (no generation)
|
|
209
|
+
bunx metanext configure --validate
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🧠 Technical Architecture
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
[ lib/seo.ts ] ← TypeScript-typed configuration
|
|
218
|
+
↓ (configure)
|
|
219
|
+
[ Scan Next.js files ] ← automatic detection
|
|
220
|
+
↓
|
|
221
|
+
[ Inject metadata ] ← into layout.tsx/page.tsx
|
|
222
|
+
↓
|
|
223
|
+
[ Server-side rendering ] ← Next.js App Router
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
✅ **Centralized configuration** in TypeScript
|
|
227
|
+
✅ **Automatic injection** into your files
|
|
228
|
+
✅ **Type-safe** with autocompletion
|
|
229
|
+
✅ **Optimized SEO** server-side
|
|
230
|
+
✅ **Simplified maintenance**
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 📘 API & Helpers
|
|
235
|
+
|
|
236
|
+
### `MetaNext` (main class)
|
|
237
|
+
Centralized SEO configuration with complete TypeScript typing.
|
|
238
|
+
|
|
239
|
+
### `configToMetadata(overrides?)`
|
|
240
|
+
Method that generates Next.js metadata from your configuration.
|
|
241
|
+
Accepts optional overrides to customize per page.
|
|
242
|
+
|
|
243
|
+
### `seoConfig.configToMetadata()`
|
|
244
|
+
Direct usage in your Next.js files to generate metadata.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 🧭 Roadmap
|
|
249
|
+
|
|
250
|
+
| Feature | Status |
|
|
251
|
+
|---------|--------|
|
|
252
|
+
| TypeScript-typed configuration | ✅ |
|
|
253
|
+
| CLI `init` / `configure` | ✅ |
|
|
254
|
+
| Automatic metadata injection | ✅ |
|
|
255
|
+
| OpenGraph and Twitter Cards support | ✅ |
|
|
256
|
+
| Local overrides per page | ✅ |
|
|
257
|
+
| SEO validation with `doctor` | ✅ |
|
|
258
|
+
| CLI `doctor` | 🔜 |
|
|
259
|
+
| Multilingual support (`hreflang`) | 🔜 |
|
|
260
|
+
| Automatic OG image generation | 🔜 |
|
|
261
|
+
| Predefined templates (`--template blog`) | 🔜 |
|
|
262
|
+
| Advanced JSON-LD support | 🔜 |
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 📦 Installation
|
|
267
|
+
|
|
268
|
+
### Requirements
|
|
269
|
+
|
|
270
|
+
- **Node.js** >= 18.0.0
|
|
271
|
+
- **Next.js** >= 14.0.0
|
|
272
|
+
- **React** >= 18.0.0
|
|
273
|
+
- **TypeScript** >= 5.9.3
|
|
274
|
+
|
|
275
|
+
### Package Managers
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Bun (recommended)
|
|
279
|
+
bun add metanext
|
|
280
|
+
|
|
281
|
+
# npm
|
|
282
|
+
npm install metanext
|
|
283
|
+
|
|
284
|
+
# pnpm
|
|
285
|
+
pnpm add metanext
|
|
286
|
+
|
|
287
|
+
# yarn
|
|
288
|
+
yarn add metanext
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 🤝 Contributing
|
|
294
|
+
|
|
295
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
296
|
+
|
|
297
|
+
### Development Setup
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Clone the repository
|
|
301
|
+
git clone https://github.com/neysixx/metanext.git
|
|
302
|
+
cd metanext
|
|
303
|
+
|
|
304
|
+
# Install dependencies
|
|
305
|
+
bun install
|
|
306
|
+
|
|
307
|
+
# Build the project
|
|
308
|
+
bun run build
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 📜 License
|
|
314
|
+
|
|
315
|
+
MIT © 2025 — Designed for modern Next.js developers 🧑💻
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## 🙏 Acknowledgments
|
|
320
|
+
|
|
321
|
+
- Built with [Next.js](https://nextjs.org/)
|
|
322
|
+
- Powered by [TypeScript](https://www.typescriptlang.org/)
|
|
323
|
+
- CLI built with [Commander.js](https://github.com/tj/commander.js)
|
|
324
|
+
- Styling with [Chalk](https://github.com/chalk/chalk)
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 📞 Support
|
|
329
|
+
|
|
330
|
+
- 📖 [Documentation](https://github.com/neysixx/metanext#readme)
|
|
331
|
+
- 🐛 [Report Issues](https://github.com/neysixx/metanext/issues)
|
|
332
|
+
- 💬 [Discussions](https://github.com/neysixx/metanext/discussions)
|
|
333
|
+
- 📧 [Email](mailto:kylliansenrens3004@gmail.com)
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
<div align="center">
|
|
338
|
+
|
|
339
|
+
**Made with ❤️ for the Next.js community**
|
|
340
|
+
|
|
341
|
+
[⭐ Star us on GitHub](https://github.com/neysixx/metanext) • [🐦 Follow on X](https://x.com/ks_nsx) • [📧 Contact](mailto:kylliansenrens3004@gmail.com)
|
|
342
|
+
|
|
343
|
+
</div>
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var commander=require('commander'),t=require('chalk'),m=require('fs-extra'),I=require('ora'),y=require('path'),v=require('prompts');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var t__default=/*#__PURE__*/_interopDefault(t);var m__default=/*#__PURE__*/_interopDefault(m);var I__default=/*#__PURE__*/_interopDefault(I);var y__default=/*#__PURE__*/_interopDefault(y);var v__default=/*#__PURE__*/_interopDefault(v);var p=new commander.Command().name("metanext").description("\u{1F9E0} MetaNext - Simplified SEO management for Next.js App Router").version("1.0.0");var $="SEOX";var c="seo.ts";var b={config:`import { MetaNext } from "metanext/next";
|
|
3
|
+
|
|
4
|
+
export const seoConfig = new MetaNext({
|
|
5
|
+
name: "{{siteName}}",
|
|
6
|
+
url: "{{baseUrl}}",
|
|
7
|
+
title: {
|
|
8
|
+
default: "{{siteName}}",
|
|
9
|
+
template: "",
|
|
10
|
+
},
|
|
11
|
+
description: "{{siteDescription}}",
|
|
12
|
+
keywords: [],
|
|
13
|
+
creator: "",
|
|
14
|
+
publisher: "",
|
|
15
|
+
authors: [
|
|
16
|
+
{
|
|
17
|
+
name: "",
|
|
18
|
+
url: "",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
manifest: "",
|
|
22
|
+
})`};function g(a){return m__default.default.existsSync(process.cwd()+"/src")?y__default.default.join(process.cwd(),"src","lib",a):y__default.default.join(process.cwd(),"lib",a)}function O(a){let r={errors:[],warnings:[],suggestions:[]};return Object.entries(a.pages||{}).forEach(([o,e])=>{e.title?e.title.length>60&&r.warnings.push(`Page "${o}" : title too long (${e.title.length} > 60)`):r.errors.push(`Page "${o}" : title missing`),e.description?e.description.length>160&&r.warnings.push(`Page "${o}" : description too long (${e.description.length} > 160)`):r.errors.push(`Page "${o}" : description missing`),(!e.jsonld||e.jsonld.length===0)&&r.suggestions.push(`Page "${o}" : add JSON-LD to improve indexing`);}),r}async function S(){let a=[],r=m__default.default.existsSync(process.cwd()+"/src")?"src":"",o=y__default.default.join(process.cwd(),r,"app"),e=y__default.default.join(process.cwd(),r,"pages");return await m__default.default.pathExists(o)&&await E(o,a),await m__default.default.pathExists(e)&&await E(e,a),a}async function E(a,r,o){let e=await m__default.default.readdir(a,{withFileTypes:true});for(let s of e){let i=y__default.default.join(a,s.name);if(s.isDirectory())await E(i,r);else if(s.isFile()&&(s.name==="layout.tsx"||s.name==="page.tsx"||s.name==="layout.ts"||s.name==="page.ts")){let n=await m__default.default.readFile(i,"utf8"),l=n.includes("export const metadata"),f=l?F(n):void 0;r.push({path:i,hasMetadata:l,metadataContent:f});}}}function F(a){let r=a.split(`
|
|
23
|
+
`),o=[],e=false,s=0;for(let i of r){if(i.includes("export const metadata")){e=true,o.push(i),s=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length;continue}if(e&&(o.push(i),s+=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length,s===0))break}return o.join(`
|
|
24
|
+
`)}async function w(a,r=false){let o=await m__default.default.readFile(a,"utf8");if(o.includes("export const metadata")&&!r)return false;let e=o;if(r&&o.includes("export const metadata")&&(e=o.replace(/export const metadata[^;]+;?\s*/gs,"")),!e.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
|
|
25
|
+
`,f=e.split(`
|
|
26
|
+
`),x=0;for(let h=0;h<f.length;h++)if(f[h].startsWith("import "))x=h+1;else if(f[h].trim()===""&&x>0)break;f.splice(x,0,l),e=f.join(`
|
|
27
|
+
`);}let s=`
|
|
28
|
+
export const metadata = seoConfig.configToMetadata();
|
|
29
|
+
`,i=e.split(`
|
|
30
|
+
`),n=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")){n=l;break}return i.splice(n,0,s),e=i.join(`
|
|
31
|
+
`),await m__default.default.writeFile(a,e,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async a=>{if(console.log(t__default.default.cyan.bold(`
|
|
32
|
+
\u{1F9E0} MetaNext - Initialization
|
|
33
|
+
`)),await m__default.default.pathExists(g(c))){let{overwrite:e}=await v__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(t__default.default.yellow("\u26A0\uFE0F Initialization canceled"));return}}let r=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:e=>e.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!r.siteName){console.log(t__default.default.red("\u274C Initialization canceled"));return}let o=I__default.default("Creating files...").start();try{await m__default.default.ensureDir(y__default.default.join(process.cwd(),"lib"));let e=b.config;Object.entries(r).forEach(([s,i])=>{e=e.replace(new RegExp(`{{${s}}}`,"g"),i);}),await m__default.default.writeFile(g(c),e,"utf8"),o.succeed("Configuration created successfully!"),console.log(t__default.default.green(`
|
|
34
|
+
\u2705 File created : `)+t__default.default.gray(g(c))),console.log(t__default.default.cyan(`
|
|
35
|
+
\u{1F4DD} Next step :`)),console.log(t__default.default.white(" bunx metanext configure")),console.log();}catch(e){o.fail("Error creating the configuration"),console.error(t__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 a=>{console.log(t__default.default.cyan.bold(`
|
|
36
|
+
\u2699\uFE0F ${$} - Configuration
|
|
37
|
+
`));let r=I__default.default("Reading configuration...").start();try{if(!await m__default.default.pathExists(g(c))){r.fail(c+" file not found"),console.log(t__default.default.yellow(`
|
|
38
|
+
\u{1F4A1} Run first: `)+t__default.default.white("bunx seox init"));return}r.text="Scanning Next.js files...";let o=await S();if(o.length===0){r.fail("No Next.js layout or page files found"),console.log(t__default.default.yellow(`
|
|
39
|
+
\u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}r.succeed(`Found ${o.length} file(s) to process`),console.log(t__default.default.cyan(`
|
|
40
|
+
\u{1F4C1} Files found:`)),o.forEach(n=>{let l=n.hasMetadata?t__default.default.yellow("(has metadata)"):t__default.default.green("(no metadata)");console.log(t__default.default.gray(` \u2022 ${n.path} ${l}`));});let e={added:0,overwritten:0,skipped:0,errors:[]},s=o.filter(n=>n.hasMetadata),i=o.filter(n=>!n.hasMetadata);console.log(t__default.default.cyan(`
|
|
41
|
+
\u{1F504} Processing files without metadata...`));for(let n of i)try{await w(n.path,!1)&&(e.added++,console.log(t__default.default.green(` \u2713 Added metadata to ${n.path}`)));}catch(l){e.errors.push(`${n.path}: ${l.message}`),console.log(t__default.default.red(` \u2717 Error in ${n.path}`));}if(s.length>0)if(console.log(t__default.default.yellow(`
|
|
42
|
+
\u26A0\uFE0F ${s.length} file(s) already have metadata exports`)),a.force){console.log(t__default.default.cyan(`
|
|
43
|
+
\u{1F504} Overwriting all existing metadata (--force)...`));for(let n of s)try{await w(n.path,!0)&&(e.overwritten++,console.log(t__default.default.yellow(` \u2713 Overwritten ${n.path}`)));}catch(l){e.errors.push(`${n.path}: ${l.message}`),console.log(t__default.default.red(` \u2717 Error in ${n.path}`));}}else {console.log(t__default.default.cyan(`
|
|
44
|
+
\u{1F504} Processing files with existing metadata...`));for(let n of s){let l=await v__default.default({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${n.path}?`,initial:!1});if(l.overwrite===void 0){console.log(t__default.default.yellow(`
|
|
45
|
+
\u274C Operation cancelled`));break}if(l.overwrite)try{await w(n.path,!0)&&(e.overwritten++,console.log(t__default.default.yellow(` \u2713 Overwritten ${n.path}`)));}catch(f){e.errors.push(`${n.path}: ${f.message}`),console.log(t__default.default.red(` \u2717 Error in ${n.path}`));}else e.skipped++,console.log(t__default.default.gray(` \u25CB Skipped ${n.path}`));}}console.log(t__default.default.green.bold(`
|
|
46
|
+
\u2705 Configuration Summary:
|
|
47
|
+
`)),console.log(t__default.default.gray(` \u2022 ${e.added} file(s) with metadata added`)),e.overwritten>0&&console.log(t__default.default.yellow(` \u2022 ${e.overwritten} file(s) overwritten`)),e.skipped>0&&console.log(t__default.default.gray(` \u2022 ${e.skipped} file(s) skipped`)),e.errors.length>0&&(console.log(t__default.default.red(` \u2022 ${e.errors.length} error(s):`)),e.errors.forEach(n=>{console.log(t__default.default.red(` - ${n}`));})),(e.added>0||e.overwritten>0)&&(console.log(t__default.default.cyan.bold(`
|
|
48
|
+
\u{1F4DD} Next Steps:
|
|
49
|
+
`)),console.log(t__default.default.white(" The following metadata export has been added to your files:")),console.log(t__default.default.gray(" import { seoConfig } from '@/lib/seo';")),console.log(t__default.default.gray(` export const metadata = seoConfig.configToMetadata();
|
|
50
|
+
`)),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(` });
|
|
51
|
+
`)),console.log(t__default.default.gray(" 2. Or manually modify the metadata object after generation")),console.log());}catch(o){r.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(t__default.default.red(o.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(t__default.default.cyan.bold(`
|
|
52
|
+
\u{1FA7A} MetaNext - Diagnostic SEO
|
|
53
|
+
`));let a=I__default.default("Analyzing...").start();try{if(!await m__default.default.pathExists(g(c))){a.fail(c+" file not found");return}let o=(await import(g(c))).default;a.stop();let e=O(o);if(e.errors.length===0&&e.warnings.length===0){console.log(t__default.default.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
|
|
54
|
+
`));return}e.errors.length>0&&(console.log(t__default.default.red.bold(`\u274C ERRORS :
|
|
55
|
+
`)),e.errors.map(s=>console.log(t__default.default.red(` \u2022 ${s}`))),console.log()),e.warnings.length>0&&(console.log(t__default.default.yellow.bold(`\u26A0\uFE0F WARNINGS :
|
|
56
|
+
`)),e.warnings.map(s=>console.log(t__default.default.yellow(` \u2022 ${s}`))),console.log()),e.suggestions.length>0&&(console.log(t__default.default.cyan.bold(`\u{1F4A1} SUGGESTIONS :
|
|
57
|
+
`)),e.suggestions.map(s=>console.log(t__default.default.cyan(` \u2022 ${s}`))),console.log());}catch(r){a.fail("Error during diagnosis"),console.error(t__default.default.red(r.message));}});p.parse(process.argv);var ye=p;module.exports=ye;
|
package/dist/cli.d.cts
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {Command}from'commander';import t from'chalk';import m from'fs-extra';import I from'ora';import y from'path';import v from'prompts';var p=new Command().name("metanext").description("\u{1F9E0} MetaNext - Simplified SEO management for Next.js App Router").version("1.0.0");var $="SEOX";var c="seo.ts";var b={config:`import { MetaNext } from "metanext/next";
|
|
3
|
+
|
|
4
|
+
export const seoConfig = new MetaNext({
|
|
5
|
+
name: "{{siteName}}",
|
|
6
|
+
url: "{{baseUrl}}",
|
|
7
|
+
title: {
|
|
8
|
+
default: "{{siteName}}",
|
|
9
|
+
template: "",
|
|
10
|
+
},
|
|
11
|
+
description: "{{siteDescription}}",
|
|
12
|
+
keywords: [],
|
|
13
|
+
creator: "",
|
|
14
|
+
publisher: "",
|
|
15
|
+
authors: [
|
|
16
|
+
{
|
|
17
|
+
name: "",
|
|
18
|
+
url: "",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
manifest: "",
|
|
22
|
+
})`};function g(a){return m.existsSync(process.cwd()+"/src")?y.join(process.cwd(),"src","lib",a):y.join(process.cwd(),"lib",a)}function O(a){let r={errors:[],warnings:[],suggestions:[]};return Object.entries(a.pages||{}).forEach(([o,e])=>{e.title?e.title.length>60&&r.warnings.push(`Page "${o}" : title too long (${e.title.length} > 60)`):r.errors.push(`Page "${o}" : title missing`),e.description?e.description.length>160&&r.warnings.push(`Page "${o}" : description too long (${e.description.length} > 160)`):r.errors.push(`Page "${o}" : description missing`),(!e.jsonld||e.jsonld.length===0)&&r.suggestions.push(`Page "${o}" : add JSON-LD to improve indexing`);}),r}async function S(){let a=[],r=m.existsSync(process.cwd()+"/src")?"src":"",o=y.join(process.cwd(),r,"app"),e=y.join(process.cwd(),r,"pages");return await m.pathExists(o)&&await E(o,a),await m.pathExists(e)&&await E(e,a),a}async function E(a,r,o){let e=await m.readdir(a,{withFileTypes:true});for(let s of e){let i=y.join(a,s.name);if(s.isDirectory())await E(i,r);else if(s.isFile()&&(s.name==="layout.tsx"||s.name==="page.tsx"||s.name==="layout.ts"||s.name==="page.ts")){let n=await m.readFile(i,"utf8"),l=n.includes("export const metadata"),f=l?F(n):void 0;r.push({path:i,hasMetadata:l,metadataContent:f});}}}function F(a){let r=a.split(`
|
|
23
|
+
`),o=[],e=false,s=0;for(let i of r){if(i.includes("export const metadata")){e=true,o.push(i),s=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length;continue}if(e&&(o.push(i),s+=(i.match(/\{/g)||[]).length-(i.match(/\}/g)||[]).length,s===0))break}return o.join(`
|
|
24
|
+
`)}async function w(a,r=false){let o=await m.readFile(a,"utf8");if(o.includes("export const metadata")&&!r)return false;let e=o;if(r&&o.includes("export const metadata")&&(e=o.replace(/export const metadata[^;]+;?\s*/gs,"")),!e.includes("import { seoConfig } from '@/lib/seo'")){let l=`import { seoConfig } from '@/lib/seo';
|
|
25
|
+
`,f=e.split(`
|
|
26
|
+
`),x=0;for(let h=0;h<f.length;h++)if(f[h].startsWith("import "))x=h+1;else if(f[h].trim()===""&&x>0)break;f.splice(x,0,l),e=f.join(`
|
|
27
|
+
`);}let s=`
|
|
28
|
+
export const metadata = seoConfig.configToMetadata();
|
|
29
|
+
`,i=e.split(`
|
|
30
|
+
`),n=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")){n=l;break}return i.splice(n,0,s),e=i.join(`
|
|
31
|
+
`),await m.writeFile(a,e,"utf8"),true}p.command("init").description("Initialize the SEOX SEO configuration").action(async a=>{if(console.log(t.cyan.bold(`
|
|
32
|
+
\u{1F9E0} MetaNext - Initialization
|
|
33
|
+
`)),await m.pathExists(g(c))){let{overwrite:e}=await v({type:"confirm",name:"overwrite",message:`The ${g(c)} file already exists. Do you want to overwrite it?`,initial:false});if(!e){console.log(t.yellow("\u26A0\uFE0F Initialization canceled"));return}}let r=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:e=>e.startsWith("http")||"Invalid URL"},{type:"text",name:"siteDescription",message:"Default description :",initial:"A modern site built with Next.js"}]);if(!r.siteName){console.log(t.red("\u274C Initialization canceled"));return}let o=I("Creating files...").start();try{await m.ensureDir(y.join(process.cwd(),"lib"));let e=b.config;Object.entries(r).forEach(([s,i])=>{e=e.replace(new RegExp(`{{${s}}}`,"g"),i);}),await m.writeFile(g(c),e,"utf8"),o.succeed("Configuration created successfully!"),console.log(t.green(`
|
|
34
|
+
\u2705 File created : `)+t.gray(g(c))),console.log(t.cyan(`
|
|
35
|
+
\u{1F4DD} Next step :`)),console.log(t.white(" bunx metanext configure")),console.log();}catch(e){o.fail("Error creating the configuration"),console.error(t.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 a=>{console.log(t.cyan.bold(`
|
|
36
|
+
\u2699\uFE0F ${$} - Configuration
|
|
37
|
+
`));let r=I("Reading configuration...").start();try{if(!await m.pathExists(g(c))){r.fail(c+" file not found"),console.log(t.yellow(`
|
|
38
|
+
\u{1F4A1} Run first: `)+t.white("bunx seox init"));return}r.text="Scanning Next.js files...";let o=await S();if(o.length===0){r.fail("No Next.js layout or page files found"),console.log(t.yellow(`
|
|
39
|
+
\u{1F4A1} Make sure you have an app/ or pages/ directory with layout.tsx/page.tsx files`));return}r.succeed(`Found ${o.length} file(s) to process`),console.log(t.cyan(`
|
|
40
|
+
\u{1F4C1} Files found:`)),o.forEach(n=>{let l=n.hasMetadata?t.yellow("(has metadata)"):t.green("(no metadata)");console.log(t.gray(` \u2022 ${n.path} ${l}`));});let e={added:0,overwritten:0,skipped:0,errors:[]},s=o.filter(n=>n.hasMetadata),i=o.filter(n=>!n.hasMetadata);console.log(t.cyan(`
|
|
41
|
+
\u{1F504} Processing files without metadata...`));for(let n of i)try{await w(n.path,!1)&&(e.added++,console.log(t.green(` \u2713 Added metadata to ${n.path}`)));}catch(l){e.errors.push(`${n.path}: ${l.message}`),console.log(t.red(` \u2717 Error in ${n.path}`));}if(s.length>0)if(console.log(t.yellow(`
|
|
42
|
+
\u26A0\uFE0F ${s.length} file(s) already have metadata exports`)),a.force){console.log(t.cyan(`
|
|
43
|
+
\u{1F504} Overwriting all existing metadata (--force)...`));for(let n of s)try{await w(n.path,!0)&&(e.overwritten++,console.log(t.yellow(` \u2713 Overwritten ${n.path}`)));}catch(l){e.errors.push(`${n.path}: ${l.message}`),console.log(t.red(` \u2717 Error in ${n.path}`));}}else {console.log(t.cyan(`
|
|
44
|
+
\u{1F504} Processing files with existing metadata...`));for(let n of s){let l=await v({type:"confirm",name:"overwrite",message:`Overwrite metadata in ${n.path}?`,initial:!1});if(l.overwrite===void 0){console.log(t.yellow(`
|
|
45
|
+
\u274C Operation cancelled`));break}if(l.overwrite)try{await w(n.path,!0)&&(e.overwritten++,console.log(t.yellow(` \u2713 Overwritten ${n.path}`)));}catch(f){e.errors.push(`${n.path}: ${f.message}`),console.log(t.red(` \u2717 Error in ${n.path}`));}else e.skipped++,console.log(t.gray(` \u25CB Skipped ${n.path}`));}}console.log(t.green.bold(`
|
|
46
|
+
\u2705 Configuration Summary:
|
|
47
|
+
`)),console.log(t.gray(` \u2022 ${e.added} file(s) with metadata added`)),e.overwritten>0&&console.log(t.yellow(` \u2022 ${e.overwritten} file(s) overwritten`)),e.skipped>0&&console.log(t.gray(` \u2022 ${e.skipped} file(s) skipped`)),e.errors.length>0&&(console.log(t.red(` \u2022 ${e.errors.length} error(s):`)),e.errors.forEach(n=>{console.log(t.red(` - ${n}`));})),(e.added>0||e.overwritten>0)&&(console.log(t.cyan.bold(`
|
|
48
|
+
\u{1F4DD} Next Steps:
|
|
49
|
+
`)),console.log(t.white(" The following metadata export has been added to your files:")),console.log(t.gray(" import { seoConfig } from '@/lib/seo';")),console.log(t.gray(` export const metadata = seoConfig.configToMetadata();
|
|
50
|
+
`)),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(` });
|
|
51
|
+
`)),console.log(t.gray(" 2. Or manually modify the metadata object after generation")),console.log());}catch(o){r.fail(`Error during configuration. You may have to check your configuration file in ${g(c)}.`),console.error(t.red(o.message)),process.exit(1);}});p.command("doctor").description("Check the validity of your SEO configuration").action(async()=>{console.log(t.cyan.bold(`
|
|
52
|
+
\u{1FA7A} MetaNext - Diagnostic SEO
|
|
53
|
+
`));let a=I("Analyzing...").start();try{if(!await m.pathExists(g(c))){a.fail(c+" file not found");return}let o=(await import(g(c))).default;a.stop();let e=O(o);if(e.errors.length===0&&e.warnings.length===0){console.log(t.green(`\u2705 No issues detected ! Your SEO configuration is optimal.
|
|
54
|
+
`));return}e.errors.length>0&&(console.log(t.red.bold(`\u274C ERRORS :
|
|
55
|
+
`)),e.errors.map(s=>console.log(t.red(` \u2022 ${s}`))),console.log()),e.warnings.length>0&&(console.log(t.yellow.bold(`\u26A0\uFE0F WARNINGS :
|
|
56
|
+
`)),e.warnings.map(s=>console.log(t.yellow(` \u2022 ${s}`))),console.log()),e.suggestions.length>0&&(console.log(t.cyan.bold(`\u{1F4A1} SUGGESTIONS :
|
|
57
|
+
`)),e.suggestions.map(s=>console.log(t.cyan(` \u2022 ${s}`))),console.log());}catch(r){a.fail("Error during diagnosis"),console.error(t.red(r.message));}});p.parse(process.argv);var ye=p;export{ye as default};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TemplateString } from 'next/dist/lib/metadata/types/metadata-types';
|
|
2
|
+
import { OpenGraphType } from 'next/dist/lib/metadata/types/opengraph-types';
|
|
3
|
+
|
|
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
|
+
}
|
|
60
|
+
interface SEOJsonLd {
|
|
61
|
+
'@context': string;
|
|
62
|
+
'@type': string;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Main SEO structure for site or page-level configuration
|
|
67
|
+
*/
|
|
68
|
+
interface SEOConfig {
|
|
69
|
+
name: string;
|
|
70
|
+
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;
|
|
83
|
+
jsonld?: SEOJsonLd[];
|
|
84
|
+
locale?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type { SEOAuthor, SEOConfig, SEOFormatDetection, SEOIcons, SEOImage, SEOJsonLd, SEOOpenGraph, SEORobots, SEOTwitter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TemplateString } from 'next/dist/lib/metadata/types/metadata-types';
|
|
2
|
+
import { OpenGraphType } from 'next/dist/lib/metadata/types/opengraph-types';
|
|
3
|
+
|
|
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
|
+
}
|
|
60
|
+
interface SEOJsonLd {
|
|
61
|
+
'@context': string;
|
|
62
|
+
'@type': string;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Main SEO structure for site or page-level configuration
|
|
67
|
+
*/
|
|
68
|
+
interface SEOConfig {
|
|
69
|
+
name: string;
|
|
70
|
+
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;
|
|
83
|
+
jsonld?: SEOJsonLd[];
|
|
84
|
+
locale?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type { SEOAuthor, SEOConfig, SEOFormatDetection, SEOIcons, SEOImage, SEOJsonLd, SEOOpenGraph, SEORobots, SEOTwitter };
|
package/dist/index.js
ADDED
|
File without changes
|
package/dist/next.cjs
ADDED
|
@@ -0,0 +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.MetaNext=n;
|
package/dist/next.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SEOConfig } from './index.cjs';
|
|
2
|
+
import { Metadata } from 'next';
|
|
3
|
+
import 'next/dist/lib/metadata/types/metadata-types';
|
|
4
|
+
import 'next/dist/lib/metadata/types/opengraph-types';
|
|
5
|
+
|
|
6
|
+
declare class MetaNext {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: SEOConfig);
|
|
9
|
+
configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
10
|
+
generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { MetaNext, SEOConfig };
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SEOConfig } from './index.js';
|
|
2
|
+
import { Metadata } from 'next';
|
|
3
|
+
import 'next/dist/lib/metadata/types/metadata-types';
|
|
4
|
+
import 'next/dist/lib/metadata/types/opengraph-types';
|
|
5
|
+
|
|
6
|
+
declare class MetaNext {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: SEOConfig);
|
|
9
|
+
configToMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
10
|
+
generatePageMetadata(overrides?: Partial<SEOConfig>): Metadata;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { MetaNext, SEOConfig };
|
package/dist/next.js
ADDED
|
@@ -0,0 +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 MetaNext};
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seox",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"seox": "./dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./next": {
|
|
22
|
+
"types": "./dist/next.d.ts",
|
|
23
|
+
"import": "./dist/next.js",
|
|
24
|
+
"require": "./dist/next.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"author": "Kyllian SENRENS <kylliansenrens3004@gmail.com>",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/neysixx/seox.git"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prepare": "husky",
|
|
35
|
+
"format:check": "biome check",
|
|
36
|
+
"format:fix": "biome check --write",
|
|
37
|
+
"build": "NODE_ENV=production tsup",
|
|
38
|
+
"build:dev": "NODE_ENV=development tsup",
|
|
39
|
+
"build:watch": "NODE_ENV=development tsup --watch",
|
|
40
|
+
"build:analyze": "ANALYZE=true NODE_ENV=production tsup",
|
|
41
|
+
"analyze": "node scripts/analyze-build.js",
|
|
42
|
+
"dev": "NODE_ENV=development tsup --watch",
|
|
43
|
+
"test": "bun test",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"prepublishOnly": "bun run build && bun run test",
|
|
46
|
+
"release": "bun run build && changeset publish"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"nextjs",
|
|
50
|
+
"seo",
|
|
51
|
+
"typescript"
|
|
52
|
+
],
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@biomejs/biome": "2.2.4",
|
|
55
|
+
"@types/bun": "latest",
|
|
56
|
+
"husky": "^9.1.7"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
60
|
+
"@semantic-release/git": "^10.0.1",
|
|
61
|
+
"@types/fs-extra": "^11.0.4",
|
|
62
|
+
"@types/prompts": "^2.4.9",
|
|
63
|
+
"chalk": "^5.3.0",
|
|
64
|
+
"commander": "^11.1.0",
|
|
65
|
+
"fs-extra": "^11.2.0",
|
|
66
|
+
"install": "^0.13.0",
|
|
67
|
+
"ora": "^7.0.1",
|
|
68
|
+
"prompts": "^2.4.2",
|
|
69
|
+
"semantic-release": "^24.2.9",
|
|
70
|
+
"tsup": "^8.5.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"next": ">=14.0.0",
|
|
75
|
+
"react": ">=18.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"next": {
|
|
79
|
+
"optional": false
|
|
80
|
+
},
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": false
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=18.0.0",
|
|
87
|
+
"bun": ">=1.0.0"
|
|
88
|
+
}
|
|
89
|
+
}
|