create-lacspace-seo 1.0.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 (4) hide show
  1. package/LICENSE +51 -0
  2. package/README.md +101 -0
  3. package/dist/index.js +344 -0
  4. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,51 @@
1
+ Lacspace Free Licence
2
+ Version 1.0, August 2026
3
+
4
+ Copyright (c) 2026 Lacspace
5
+
6
+ PREAMBLE
7
+
8
+ This software is published by Lacspace under the Lacspace Free Licence — a free,
9
+ permissive licence that lets you use this software for any purpose, including in
10
+ commercial products and services, at no cost. It grants the same freedoms as
11
+ common permissive open-source licences; the only condition is that this notice
12
+ travels with the software. The canonical, always-current text of this licence is
13
+ maintained at https://lacspace.com/licenses/lacspace-free-1.0
14
+
15
+ GRANT OF RIGHTS
16
+
17
+ Permission is hereby granted, free of charge, to any person or organisation
18
+ obtaining a copy of this software and its associated documentation and data files
19
+ (the "Software"), to deal in the Software without restriction, including without
20
+ limitation the rights to use, copy, modify, merge, publish, distribute,
21
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the
22
+ Software is furnished to do so, subject to the conditions below. These rights are
23
+ granted for any purpose, personal or commercial, and are perpetual, worldwide,
24
+ non-exclusive, and royalty-free.
25
+
26
+ CONDITIONS
27
+
28
+ The above copyright notice, this permission notice, and the name of this licence
29
+ ("Lacspace Free Licence") shall be included in all copies or substantial portions
30
+ of the Software.
31
+
32
+ TRADEMARKS
33
+
34
+ This licence does not grant permission to use the trade names, trademarks, service
35
+ marks, logos, or product names of Lacspace, except as required to reproduce the
36
+ notice above or to describe the origin of the Software in a truthful manner.
37
+
38
+ DISCLAIMER OF WARRANTY AND LIMITATION OF LIABILITY
39
+
40
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
42
+ FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
43
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN
44
+ AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
45
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46
+
47
+ ---
48
+
49
+ The Lacspace Free Licence is a source-available, permissive licence and is not (as
50
+ of this version) an OSI-approved licence. In substance it grants the same freedoms
51
+ as the MIT Licence. Learn more at https://lacspace.com/licenses
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ <div align="center">
2
+
3
+ # create-lacspace-seo
4
+
5
+ **Scaffold a complete, production-ready SEO setup into a Next.js app in one command.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/create-lacspace-seo?color=%2316a34a&label=npm)](https://www.npmjs.com/package/create-lacspace-seo)
8
+ [![license](https://img.shields.io/npm/l/create-lacspace-seo?color=green)](https://github.com/lacspace/npm-packages/blob/main/LICENSE)
9
+
10
+ </div>
11
+
12
+ > One command drops in a `defineSite()` config plus **ready-made `robots.txt`, `sitemap.xml`, `feed.xml`, `llms.txt` and a dynamic OG-image route** — all wired to a single source of truth. No copywriting, no boilerplate, no fragile hand-written structured data.
13
+
14
+ ## Use it
15
+
16
+ ```bash
17
+ npm create lacspace-seo@latest
18
+ # or
19
+ npx create-lacspace-seo
20
+ ```
21
+
22
+ Run it inside a Next.js **App Router** project. It auto-detects `app/` or `src/app/`, asks a few questions (or take flags), and writes:
23
+
24
+ ```
25
+ lib/site.ts ← one defineSite() config, used everywhere
26
+ app/robots.txt/route.ts ← robots.txt (auto sitemap ref, optional AI blocking)
27
+ app/sitemap.xml/route.ts ← sitemap.xml from bare paths
28
+ app/feed.xml/route.ts ← RSS feed, prefilled from the site config
29
+ app/llms.txt/route.ts ← llms.txt map for LLMs (llmstxt.org)
30
+ app/og/route.tsx ← dynamic Open Graph images (/og?title=…)
31
+ ```
32
+
33
+ Then install the packages it uses and you're done:
34
+
35
+ ```bash
36
+ npm i @lacspace/seo @lacspace/robots @lacspace/sitemap @lacspace/rss @lacspace/llms-txt
37
+ ```
38
+
39
+ ## Non-interactive (CI / scripts)
40
+
41
+ ```bash
42
+ npx create-lacspace-seo --yes \
43
+ --name "Acme" \
44
+ --url "https://acme.com" \
45
+ --description "We build things" \
46
+ --twitter acmehq \
47
+ --logo /logo.png
48
+ ```
49
+
50
+ | Flag | Meaning |
51
+ | --- | --- |
52
+ | `--name <name>` | Site / brand name |
53
+ | `--url <url>` | Absolute site URL |
54
+ | `--description <text>` | Default meta description |
55
+ | `--twitter <handle>` | Twitter/X handle |
56
+ | `--logo <path>` | Logo path (e.g. `/logo.png`) |
57
+ | `--no-og` | Skip the dynamic OG-image route |
58
+ | `-y, --yes` | No prompts (needs `--name` & `--url`) |
59
+ | `--force` | Overwrite existing files |
60
+
61
+ Existing files are **never overwritten** unless you pass `--force` — safe to re-run.
62
+
63
+ ## What you get
64
+
65
+ Every page becomes a one-liner, powered by the file it generated:
66
+
67
+ ```ts
68
+ import { site } from "@/lib/site";
69
+
70
+ export const metadata = site.meta({ title: "Pricing", path: "/pricing" });
71
+ // → title template, canonical, Open Graph, Twitter card, dynamic OG image — all filled in
72
+ ```
73
+
74
+ ## The Lacspace SEO Kit
75
+
76
+ | Package | For |
77
+ | --- | --- |
78
+ | [`@lacspace/seo`](https://www.npmjs.com/package/@lacspace/seo) | Metadata, JSON-LD & the `defineSite()` engine |
79
+ | [`@lacspace/sitemap`](https://www.npmjs.com/package/@lacspace/sitemap) | sitemap.xml |
80
+ | [`@lacspace/robots`](https://www.npmjs.com/package/@lacspace/robots) | robots.txt |
81
+ | [`@lacspace/llms-txt`](https://www.npmjs.com/package/@lacspace/llms-txt) | llms.txt / llms-full.txt |
82
+ | [`@lacspace/rss`](https://www.npmjs.com/package/@lacspace/rss) | RSS / Atom / JSON feeds |
83
+ | **`create-lacspace-seo`** | Scaffolder (this package) |
84
+
85
+ ## Licensing
86
+
87
+ This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
88
+
89
+ Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
90
+
91
+ ---
92
+
93
+ <div align="center">
94
+
95
+ **Part of the Lacspace ecosystem — zero-dependency, isomorphic TypeScript packages.**
96
+
97
+ [All packages ↗](https://lacspace.com/packages) · [npm org ↗](https://www.npmjs.com/org/lacspace) · [Licence Centre ↗](https://lacspace.com/licenses) · [GitHub ↗](https://github.com/lacspace/npm-packages)
98
+
99
+ </div>
100
+
101
+ <div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
package/dist/index.js ADDED
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
3
+ import { join, resolve, dirname } from 'path';
4
+ import { createInterface } from 'readline/promises';
5
+ import { stdout, exit, argv, cwd, stdin } from 'process';
6
+
7
+ var C = {
8
+ reset: "\x1B[0m",
9
+ bold: "\x1B[1m",
10
+ dim: "\x1B[2m",
11
+ green: "\x1B[32m",
12
+ cyan: "\x1B[36m",
13
+ yellow: "\x1B[33m",
14
+ red: "\x1B[31m",
15
+ blue: "\x1B[34m"
16
+ };
17
+ var c = (color, s) => `${C[color]}${s}${C.reset}`;
18
+ function parseArgs(list) {
19
+ const a = { yes: false, force: false, og: true, help: false };
20
+ for (let i = 0; i < list.length; i++) {
21
+ const arg = list[i];
22
+ const next = () => list[++i] ?? "";
23
+ switch (arg) {
24
+ case "--name":
25
+ a.name = next();
26
+ break;
27
+ case "--url":
28
+ a.url = next();
29
+ break;
30
+ case "--description":
31
+ case "--desc":
32
+ a.description = next();
33
+ break;
34
+ case "--twitter":
35
+ a.twitter = next();
36
+ break;
37
+ case "--logo":
38
+ a.logo = next();
39
+ break;
40
+ case "-y":
41
+ case "--yes":
42
+ a.yes = true;
43
+ break;
44
+ case "--force":
45
+ a.force = true;
46
+ break;
47
+ case "--no-og":
48
+ a.og = false;
49
+ break;
50
+ case "-h":
51
+ case "--help":
52
+ a.help = true;
53
+ break;
54
+ default:
55
+ if (arg.startsWith("--")) {
56
+ const eq = arg.indexOf("=");
57
+ if (eq !== -1) {
58
+ const key = arg.slice(2, eq);
59
+ const val = arg.slice(eq + 1);
60
+ if (key === "name") a.name = val;
61
+ else if (key === "url") a.url = val;
62
+ else if (key === "description" || key === "desc") a.description = val;
63
+ else if (key === "twitter") a.twitter = val;
64
+ else if (key === "logo") a.logo = val;
65
+ }
66
+ }
67
+ }
68
+ }
69
+ return a;
70
+ }
71
+ var HELP = `
72
+ ${c("bold", "create-lacspace-seo")} \u2014 scaffold a full SEO setup into a Next.js app
73
+
74
+ ${c("bold", "Usage")}
75
+ npm create lacspace-seo@latest
76
+ npx create-lacspace-seo [options]
77
+
78
+ ${c("bold", "Options")}
79
+ --name <name> Site / brand name
80
+ --url <url> Absolute site URL (https://\u2026)
81
+ --description <text> Default meta description
82
+ --twitter <handle> Twitter/X handle (with or without @)
83
+ --logo <path> Logo path (e.g. /logo.png)
84
+ --no-og Skip the dynamic OG-image route
85
+ -y, --yes Accept defaults, no prompts (needs --name & --url)
86
+ --force Overwrite existing files
87
+ -h, --help Show this help
88
+ `;
89
+ function detectLayout(root) {
90
+ if (existsSync(join(root, "src", "app"))) {
91
+ return { root, appDir: join(root, "src", "app"), libDir: join(root, "src", "lib"), usesSrc: true };
92
+ }
93
+ if (existsSync(join(root, "app"))) {
94
+ return { root, appDir: join(root, "app"), libDir: join(root, "lib"), usesSrc: false };
95
+ }
96
+ return null;
97
+ }
98
+ function isNextProject(root) {
99
+ const pkgPath = join(root, "package.json");
100
+ if (!existsSync(pkgPath)) return false;
101
+ try {
102
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
103
+ return Boolean(pkg.dependencies?.["next"] ?? pkg.devDependencies?.["next"]);
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+ var q = (s) => JSON.stringify(s);
109
+ function siteTemplate(cfg) {
110
+ const lines = [
111
+ `import { defineSite } from "@lacspace/seo";`,
112
+ ``,
113
+ `/**`,
114
+ ` * Your site's SEO configuration \u2014 set ONCE, used everywhere.`,
115
+ ` * Generated by create-lacspace-seo.`,
116
+ ` */`,
117
+ `export const site = defineSite({`,
118
+ ` name: ${q(cfg.name)},`,
119
+ ` url: ${q(cfg.url)},`
120
+ ];
121
+ if (cfg.description) lines.push(` description: ${q(cfg.description)},`);
122
+ if (cfg.logo) lines.push(` logo: ${q(cfg.logo)},`);
123
+ if (cfg.twitter) lines.push(` twitter: ${q(cfg.twitter)},`);
124
+ if (cfg.og) lines.push(` ogImage: "/og", // dynamic social cards \u2192 /og?title=<page>`);
125
+ lines.push(` // searchUrl: ${q(cfg.url.replace(/\/$/, "") + "/search?q={search_term_string}")},`);
126
+ lines.push(`});`, ``);
127
+ return lines.join("\n");
128
+ }
129
+ var robotsTemplate = () => `import { robotsForSite } from "@lacspace/robots";
130
+ import { site } from "@/lib/site";
131
+
132
+ // Served at /robots.txt \u2014 flip blockAi to true to fend off AI scrapers.
133
+ export function GET() {
134
+ return new Response(robotsForSite(site.config, { blockAi: false }), {
135
+ headers: { "content-type": "text/plain" },
136
+ });
137
+ }
138
+ `;
139
+ var sitemapTemplate = () => `import { sitemapForSite } from "@lacspace/sitemap";
140
+ import { site } from "@/lib/site";
141
+
142
+ // Served at /sitemap.xml \u2014 add your static + dynamic routes here.
143
+ export function GET() {
144
+ const xml = sitemapForSite(site.config, [
145
+ "/",
146
+ "/about",
147
+ // { path: "/blog", changefreq: "daily", priority: 0.8 },
148
+ ]);
149
+ return new Response(xml, { headers: { "content-type": "application/xml" } });
150
+ }
151
+ `;
152
+ var feedTemplate = () => `import { rss, feedForSite } from "@lacspace/rss";
153
+ import { site } from "@/lib/site";
154
+
155
+ // Served at /feed.xml \u2014 map your posts into feed items.
156
+ export function GET() {
157
+ const posts: { title: string; link: string; date: string; description?: string }[] = [
158
+ // { title: "Hello world", link: site.url("/blog/hello"), date: "2026-01-01" },
159
+ ];
160
+ return new Response(rss(feedForSite(site.config, "/feed.xml"), posts), {
161
+ headers: { "content-type": "application/rss+xml" },
162
+ });
163
+ }
164
+ `;
165
+ var llmsTemplate = () => `import { llmsTxt } from "@lacspace/llms-txt";
166
+ import { site } from "@/lib/site";
167
+
168
+ // Served at /llms.txt \u2014 a curated map for LLMs (llmstxt.org).
169
+ export function GET() {
170
+ const txt = llmsTxt({
171
+ title: site.config.name,
172
+ summary: site.config.description ?? "",
173
+ sections: [
174
+ {
175
+ title: "Pages",
176
+ links: [
177
+ { title: "Home", url: site.url("/") },
178
+ { title: "About", url: site.url("/about") },
179
+ ],
180
+ },
181
+ ],
182
+ });
183
+ return new Response(txt, { headers: { "content-type": "text/plain" } });
184
+ }
185
+ `;
186
+ var ogTemplate = () => `import { ImageResponse } from "next/og";
187
+ import { site } from "@/lib/site";
188
+
189
+ export const runtime = "edge";
190
+
191
+ // Served at /og?title=... \u2014 dynamic Open Graph card, no design tool needed.
192
+ export function GET(req: Request) {
193
+ const title = new URL(req.url).searchParams.get("title") ?? site.config.name;
194
+ return new ImageResponse(
195
+ (
196
+ <div
197
+ style={{
198
+ width: "100%",
199
+ height: "100%",
200
+ display: "flex",
201
+ flexDirection: "column",
202
+ justifyContent: "center",
203
+ padding: "80px",
204
+ background: "linear-gradient(135deg, #0b1220, #1e293b)",
205
+ color: "white",
206
+ fontFamily: "sans-serif",
207
+ }}
208
+ >
209
+ <div style={{ fontSize: 68, fontWeight: 800, lineHeight: 1.1 }}>{title}</div>
210
+ <div style={{ marginTop: 24, fontSize: 30, opacity: 0.7 }}>{site.config.name}</div>
211
+ </div>
212
+ ),
213
+ { width: 1200, height: 630 },
214
+ );
215
+ }
216
+ `;
217
+ function writePlan(plans, force) {
218
+ const written = [];
219
+ const skipped = [];
220
+ for (const p of plans) {
221
+ if (existsSync(p.path) && !force) {
222
+ skipped.push(p.rel);
223
+ continue;
224
+ }
225
+ mkdirSync(dirname(p.path), { recursive: true });
226
+ writeFileSync(p.path, p.content);
227
+ written.push(p.rel);
228
+ }
229
+ return { written, skipped };
230
+ }
231
+ async function main() {
232
+ const args = parseArgs(argv.slice(2));
233
+ if (args.help) {
234
+ stdout.write(HELP + "\n");
235
+ return;
236
+ }
237
+ stdout.write(`
238
+ ${c("bold", c("cyan", "\u25C6 create-lacspace-seo"))} ${c("dim", "\u2014 full SEO setup for Next.js")}
239
+
240
+ `);
241
+ const root = cwd();
242
+ if (!isNextProject(root)) {
243
+ stdout.write(c("yellow", '! No "next" dependency found in package.json.\n'));
244
+ stdout.write(c("dim", " Run this inside a Next.js App Router project.\n\n"));
245
+ if (args.yes) exit(1);
246
+ }
247
+ const layout = detectLayout(root);
248
+ if (!layout) {
249
+ stdout.write(c("red", "\u2717 Could not find an app/ or src/app/ directory.\n"));
250
+ stdout.write(c("dim", " This scaffolder targets the Next.js App Router.\n\n"));
251
+ exit(1);
252
+ return;
253
+ }
254
+ let name = args.name;
255
+ let url = args.url;
256
+ let description = args.description;
257
+ let twitter = args.twitter;
258
+ let logo = args.logo;
259
+ let og = args.og;
260
+ if (!args.yes) {
261
+ const rl = createInterface({ input: stdin, output: stdout });
262
+ try {
263
+ const ask = async (label, fallback = "") => {
264
+ const suffix = fallback ? c("dim", ` (${fallback})`) : "";
265
+ const ans = (await rl.question(`${c("green", "?")} ${label}${suffix}: `)).trim();
266
+ return ans || fallback;
267
+ };
268
+ if (!name) name = await ask("Site / brand name", "My Site");
269
+ if (!url) url = await ask("Site URL", "https://example.com");
270
+ if (description === void 0) description = await ask("Default description") || void 0;
271
+ if (twitter === void 0) twitter = await ask("Twitter/X handle (optional)") || void 0;
272
+ if (logo === void 0) logo = await ask("Logo path (optional, e.g. /logo.png)") || void 0;
273
+ const ogAns = (await ask("Add dynamic OG-image route? (Y/n)", "Y")).toLowerCase();
274
+ og = ogAns !== "n" && ogAns !== "no";
275
+ } finally {
276
+ rl.close();
277
+ }
278
+ }
279
+ if (!name || !url) {
280
+ stdout.write(c("red", "\n\u2717 --name and --url are required (with --yes).\n\n"));
281
+ exit(1);
282
+ return;
283
+ }
284
+ const cfg = { name, url, description, twitter, logo, og };
285
+ const { appDir, libDir } = layout;
286
+ const plans = [
287
+ { path: join(libDir, "site.ts"), rel: relTo(root, join(libDir, "site.ts")), content: siteTemplate(cfg) },
288
+ { path: join(appDir, "robots.txt", "route.ts"), rel: relTo(root, join(appDir, "robots.txt", "route.ts")), content: robotsTemplate() },
289
+ { path: join(appDir, "sitemap.xml", "route.ts"), rel: relTo(root, join(appDir, "sitemap.xml", "route.ts")), content: sitemapTemplate() },
290
+ { path: join(appDir, "feed.xml", "route.ts"), rel: relTo(root, join(appDir, "feed.xml", "route.ts")), content: feedTemplate() },
291
+ { path: join(appDir, "llms.txt", "route.ts"), rel: relTo(root, join(appDir, "llms.txt", "route.ts")), content: llmsTemplate() }
292
+ ];
293
+ if (og) {
294
+ plans.push({ path: join(appDir, "og", "route.tsx"), rel: relTo(root, join(appDir, "og", "route.tsx")), content: ogTemplate() });
295
+ }
296
+ const { written, skipped } = writePlan(plans, args.force);
297
+ stdout.write("\n");
298
+ for (const w of written) stdout.write(` ${c("green", "+")} ${w}
299
+ `);
300
+ for (const s of skipped) stdout.write(` ${c("yellow", "\u2022")} ${s} ${c("dim", "(exists, skipped \u2014 use --force)")}
301
+ `);
302
+ const deps = ["@lacspace/seo", "@lacspace/robots", "@lacspace/sitemap", "@lacspace/rss", "@lacspace/llms-txt"];
303
+ stdout.write(`
304
+ ${c("bold", "Next steps")}
305
+ `);
306
+ stdout.write(` 1. Install the SEO packages:
307
+ `);
308
+ stdout.write(` ${c("cyan", `npm i ${deps.join(" ")}`)}
309
+ `);
310
+ stdout.write(` 2. Edit ${c("cyan", relTo(root, join(libDir, "site.ts")))} \u2014 that's your one source of truth.
311
+ `);
312
+ stdout.write(` 3. Use it in any page:
313
+ `);
314
+ stdout.write(` ${c("dim", `import { site } from "@/lib/site";`)}
315
+ `);
316
+ stdout.write(` ${c("dim", `export const metadata = site.meta({ title: "Pricing", path: "/pricing" });`)}
317
+ `);
318
+ stdout.write(` 4. Drop the sitewide JSON-LD into app/layout.tsx once:
319
+ `);
320
+ stdout.write(` ${c("dim", `import { jsonLdScript } from "@lacspace/seo";`)}
321
+ `);
322
+ stdout.write(` ${c("dim", `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(site.rootJsonLd()) }} />`)}
323
+ `);
324
+ if (skipped.length) {
325
+ stdout.write(`
326
+ ${c("yellow", `${skipped.length} file(s) already existed`)} \u2014 re-run with ${c("cyan", "--force")} to overwrite.
327
+ `);
328
+ }
329
+ stdout.write(`
330
+ ${c("green", "\u2713 SEO on autopilot.")} ${c("dim", "Docs: https://lacspace.com/packages")}
331
+
332
+ `);
333
+ }
334
+ function relTo(root, p) {
335
+ const r = resolve(p).slice(resolve(root).length).replace(/^[/\\]/, "");
336
+ return r || p;
337
+ }
338
+ main().catch((err) => {
339
+ stdout.write(c("red", `
340
+ \u2717 ${err instanceof Error ? err.message : String(err)}
341
+
342
+ `));
343
+ exit(1);
344
+ });
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "create-lacspace-seo",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a complete, production-ready SEO setup into a Next.js App Router app in seconds — defineSite() config plus ready-made robots.txt, sitemap.xml, feed.xml, llms.txt and a dynamic OG-image route. Zero config, zero copywriting.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-lacspace-seo": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "create-lacspace-seo",
18
+ "seo",
19
+ "scaffold",
20
+ "generator",
21
+ "nextjs",
22
+ "next-seo",
23
+ "robots-txt",
24
+ "sitemap",
25
+ "llms-txt",
26
+ "rss-feed",
27
+ "og-image",
28
+ "structured-data",
29
+ "boilerplate",
30
+ "cli",
31
+ "typescript"
32
+ ],
33
+ "author": "Lacspace <contact@lacspace.com>",
34
+ "license": "SEE LICENSE IN LICENSE",
35
+ "homepage": "https://lacspace.com/packages",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/lacspace/npm-packages.git",
39
+ "directory": "create-lacspace-seo"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/lacspace/npm-packages/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }