@seo-console/package 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -305,13 +305,115 @@ Add a link to the SEO admin section in your main admin navigation:
305
305
  </nav>
306
306
  ```
307
307
 
308
+ ### Step 5: Discover and Import Existing Pages (Optional but Recommended)
309
+
310
+ **⚠️ IMPORTANT:** The package doesn't automatically detect your existing pages or SEO settings. You need to discover and import them.
311
+
312
+ **Create `app/api/discover-routes/route.ts` to find all your pages:**
313
+
314
+ ```typescript
315
+ import { NextResponse } from "next/server";
316
+ import { discoverNextJSRoutes } from "@seo-console/package/server";
317
+
318
+ export async function POST() {
319
+ try {
320
+ const appDir = process.env.NEXT_PUBLIC_APP_DIR || "app";
321
+ const routes = await discoverNextJSRoutes(appDir);
322
+ return NextResponse.json({ routes });
323
+ } catch (error) {
324
+ return NextResponse.json(
325
+ { error: error instanceof Error ? error.message : "Failed to discover routes" },
326
+ { status: 500 }
327
+ );
328
+ }
329
+ }
330
+ ```
331
+
332
+ **Create `app/api/import-from-site/route.ts` to import existing SEO metadata:**
333
+
334
+ ```typescript
335
+ import { NextRequest, NextResponse } from "next/server";
336
+ import { detectStorageConfig, createStorageAdapter } from "@seo-console/package";
337
+ import { extractMetadataFromURL, metadataToSEORecord } from "@seo-console/package/server";
338
+
339
+ export async function POST(request: NextRequest) {
340
+ try {
341
+ const { baseUrl, routes } = await request.json();
342
+
343
+ if (!baseUrl) {
344
+ return NextResponse.json({ error: "Base URL is required" }, { status: 400 });
345
+ }
346
+
347
+ const routesToImport = Array.isArray(routes) && routes.length > 0 ? routes : ["/"];
348
+ const results: Array<{ route: string; success: boolean; error?: string }> = [];
349
+
350
+ const config = detectStorageConfig();
351
+ const storage = createStorageAdapter(config);
352
+
353
+ for (const route of routesToImport) {
354
+ try {
355
+ const url = new URL(route, baseUrl).toString();
356
+ const metadata = await extractMetadataFromURL(url);
357
+
358
+ if (Object.keys(metadata).length === 0) {
359
+ results.push({ route, success: false, error: "No metadata found" });
360
+ continue;
361
+ }
362
+
363
+ const recordData = metadataToSEORecord(metadata, route, "file-user");
364
+ await storage.createRecord(recordData as { userId: string; routePath: string; [key: string]: unknown });
365
+ results.push({ route, success: true });
366
+
367
+ // Rate limiting - wait 200ms between requests
368
+ await new Promise((resolve) => setTimeout(resolve, 200));
369
+ } catch (err) {
370
+ results.push({
371
+ route,
372
+ success: false,
373
+ error: err instanceof Error ? err.message : "Unknown error",
374
+ });
375
+ }
376
+ }
377
+
378
+ return NextResponse.json({ results });
379
+ } catch (error) {
380
+ return NextResponse.json(
381
+ { error: error instanceof Error ? error.message : "Failed to import from site" },
382
+ { status: 500 }
383
+ );
384
+ }
385
+ }
386
+ ```
387
+
388
+ **How to use these routes:**
389
+
390
+ 1. **Discover routes:** Call `POST /api/discover-routes` to get a list of all your pages
391
+ 2. **Import SEO:** Call `POST /api/import-from-site` with your site's base URL and the routes you want to import
392
+
393
+ Example:
394
+ ```typescript
395
+ // In your admin UI or a script
396
+ const discoverResponse = await fetch("/api/discover-routes", { method: "POST" });
397
+ const { routes } = await discoverResponse.json();
398
+
399
+ const importResponse = await fetch("/api/import-from-site", {
400
+ method: "POST",
401
+ headers: { "Content-Type": "application/json" },
402
+ body: JSON.stringify({
403
+ baseUrl: "https://yoursite.com",
404
+ routes: routes.map((r: any) => r.routePath)
405
+ })
406
+ });
407
+ ```
408
+
308
409
  ## Quick Start Summary
309
410
 
310
411
  1. **Install:** `npm install @seo-console/package`
311
412
  2. **Create API route:** `app/api/seo-records/route.ts` (see Step 2)
312
413
  3. **Create admin pages:** `app/admin/seo/` directory with pages (see Step 3)
313
414
  4. **Add to navigation:** Link to `/admin/seo` in your admin menu
314
- 5. **Use in pages:** Add `generateMetadata` to your pages (see Step 4)
415
+ 5. **Discover & import pages:** Create `app/api/discover-routes/route.ts` and `app/api/import-from-site/route.ts` (see Step 5)
416
+ 6. **Use in pages:** Add `generateMetadata` to your pages (see Step 4)
315
417
 
316
418
  That's it! The SEO admin interface will be accessible at `/admin/seo` as a new tab in your admin area.
317
419
 
package/dist/index.d.mts CHANGED
@@ -1,83 +1,6 @@
1
1
  export { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, OGImagePreview, SEORecordForm, SEORecordList, Spinner, ValidationDashboard } from './components/index.mjs';
2
- import { S as SEORecord, C as CreateSEORecord, U as UpdateSEORecord } from './seo-schema-D8EwzllB.mjs';
3
- export { c as createSEORecordSchema, u as updateSEORecordSchema } from './seo-schema-D8EwzllB.mjs';
4
- export { D as DiscoveredRoute, E as ExtractedMetadata, I as ImageValidationResult, R as RobotsTxtOptions, S as SitemapEntry, n as SitemapOptions, c as ValidationIssue, V as ValidationResult, h as crawlSiteForSEO, i as discoverNextJSRoutes, k as extractMetadataFromHTML, f as extractMetadataFromURL, o as extractSitemapFromRobotsTxt, j as generateExamplePaths, e as generateRobotsTxt, d as generateSitemapFromRecords, g as generateSitemapXML, m as metadataToSEORecord, s as seoRecordsToSitemapEntries, u as updateRobotsTxtWithSitemap, l as validateSitemapEntry } from './robots-generator-9d9aTULk.mjs';
2
+ export { C as CreateSEORecord, S as SEORecord, U as UpdateSEORecord, c as createSEORecordSchema, u as updateSEORecordSchema } from './seo-schema-D8EwzllB.mjs';
3
+ export { E as ExtractedMetadata, I as ImageValidationResult, R as RobotsTxtOptions, S as SitemapEntry, c as SitemapOptions, h as StorageAdapter, j as StorageConfig, i as StorageType, a as ValidationIssue, V as ValidationResult, k as detectStorageConfig, e as extractMetadataFromHTML, f as extractSitemapFromRobotsTxt, d as generateRobotsTxt, b as generateSitemapFromRecords, g as generateSitemapXML, m as metadataToSEORecord, s as seoRecordsToSitemapEntries, u as updateRobotsTxtWithSitemap, v as validateSitemapEntry } from './storage-factory-CdCI1VHl.mjs';
5
4
  import 'react/jsx-runtime';
6
5
  import 'react';
7
6
  import 'zod';
8
-
9
- /**
10
- * Storage Adapter Interface
11
- * Allows SEO Console to work with different storage backends
12
- */
13
-
14
- interface StorageAdapter {
15
- /**
16
- * Get all SEO records
17
- */
18
- getRecords(): Promise<SEORecord[]>;
19
- /**
20
- * Get a single SEO record by ID
21
- */
22
- getRecordById(id: string): Promise<SEORecord | null>;
23
- /**
24
- * Get SEO record by route path
25
- */
26
- getRecordByRoute(routePath: string): Promise<SEORecord | null>;
27
- /**
28
- * Create a new SEO record
29
- */
30
- createRecord(record: CreateSEORecord): Promise<SEORecord>;
31
- /**
32
- * Update an existing SEO record
33
- */
34
- updateRecord(record: UpdateSEORecord): Promise<SEORecord>;
35
- /**
36
- * Delete an SEO record
37
- */
38
- deleteRecord(id: string): Promise<void>;
39
- /**
40
- * Check if storage is available/configured
41
- */
42
- isAvailable(): Promise<boolean>;
43
- }
44
- type StorageType = "file" | "memory";
45
- interface StorageConfig {
46
- type?: StorageType;
47
- filePath?: string;
48
- }
49
-
50
- /**
51
- * Storage Factory
52
- * Creates file-based storage adapter
53
- */
54
-
55
- declare function createStorageAdapter(config?: StorageConfig): StorageAdapter;
56
- /**
57
- * Auto-detect storage config from environment
58
- */
59
- declare function detectStorageConfig(): StorageConfig;
60
-
61
- /**
62
- * File-based storage adapter
63
- * Stores SEO records in a JSON file
64
- * No database required!
65
- */
66
-
67
- declare class FileStorage implements StorageAdapter {
68
- private filePath;
69
- private records;
70
- private initialized;
71
- constructor(filePath?: string);
72
- private ensureInitialized;
73
- private save;
74
- isAvailable(): Promise<boolean>;
75
- getRecords(): Promise<SEORecord[]>;
76
- getRecordById(id: string): Promise<SEORecord | null>;
77
- getRecordByRoute(routePath: string): Promise<SEORecord | null>;
78
- createRecord(record: CreateSEORecord): Promise<SEORecord>;
79
- updateRecord(record: UpdateSEORecord): Promise<SEORecord>;
80
- deleteRecord(id: string): Promise<void>;
81
- }
82
-
83
- export { CreateSEORecord, FileStorage, SEORecord, type StorageAdapter, type StorageConfig, type StorageType, UpdateSEORecord, createStorageAdapter, detectStorageConfig };
package/dist/index.d.ts CHANGED
@@ -1,83 +1,6 @@
1
1
  export { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, OGImagePreview, SEORecordForm, SEORecordList, Spinner, ValidationDashboard } from './components/index.js';
2
- import { S as SEORecord, C as CreateSEORecord, U as UpdateSEORecord } from './seo-schema-D8EwzllB.js';
3
- export { c as createSEORecordSchema, u as updateSEORecordSchema } from './seo-schema-D8EwzllB.js';
4
- export { D as DiscoveredRoute, E as ExtractedMetadata, I as ImageValidationResult, R as RobotsTxtOptions, S as SitemapEntry, n as SitemapOptions, c as ValidationIssue, V as ValidationResult, h as crawlSiteForSEO, i as discoverNextJSRoutes, k as extractMetadataFromHTML, f as extractMetadataFromURL, o as extractSitemapFromRobotsTxt, j as generateExamplePaths, e as generateRobotsTxt, d as generateSitemapFromRecords, g as generateSitemapXML, m as metadataToSEORecord, s as seoRecordsToSitemapEntries, u as updateRobotsTxtWithSitemap, l as validateSitemapEntry } from './robots-generator-CYA9Ofu_.js';
2
+ export { C as CreateSEORecord, S as SEORecord, U as UpdateSEORecord, c as createSEORecordSchema, u as updateSEORecordSchema } from './seo-schema-D8EwzllB.js';
3
+ export { E as ExtractedMetadata, I as ImageValidationResult, R as RobotsTxtOptions, S as SitemapEntry, c as SitemapOptions, h as StorageAdapter, j as StorageConfig, i as StorageType, a as ValidationIssue, V as ValidationResult, k as detectStorageConfig, e as extractMetadataFromHTML, f as extractSitemapFromRobotsTxt, d as generateRobotsTxt, b as generateSitemapFromRecords, g as generateSitemapXML, m as metadataToSEORecord, s as seoRecordsToSitemapEntries, u as updateRobotsTxtWithSitemap, v as validateSitemapEntry } from './storage-factory-L2YGjVID.js';
5
4
  import 'react/jsx-runtime';
6
5
  import 'react';
7
6
  import 'zod';
8
-
9
- /**
10
- * Storage Adapter Interface
11
- * Allows SEO Console to work with different storage backends
12
- */
13
-
14
- interface StorageAdapter {
15
- /**
16
- * Get all SEO records
17
- */
18
- getRecords(): Promise<SEORecord[]>;
19
- /**
20
- * Get a single SEO record by ID
21
- */
22
- getRecordById(id: string): Promise<SEORecord | null>;
23
- /**
24
- * Get SEO record by route path
25
- */
26
- getRecordByRoute(routePath: string): Promise<SEORecord | null>;
27
- /**
28
- * Create a new SEO record
29
- */
30
- createRecord(record: CreateSEORecord): Promise<SEORecord>;
31
- /**
32
- * Update an existing SEO record
33
- */
34
- updateRecord(record: UpdateSEORecord): Promise<SEORecord>;
35
- /**
36
- * Delete an SEO record
37
- */
38
- deleteRecord(id: string): Promise<void>;
39
- /**
40
- * Check if storage is available/configured
41
- */
42
- isAvailable(): Promise<boolean>;
43
- }
44
- type StorageType = "file" | "memory";
45
- interface StorageConfig {
46
- type?: StorageType;
47
- filePath?: string;
48
- }
49
-
50
- /**
51
- * Storage Factory
52
- * Creates file-based storage adapter
53
- */
54
-
55
- declare function createStorageAdapter(config?: StorageConfig): StorageAdapter;
56
- /**
57
- * Auto-detect storage config from environment
58
- */
59
- declare function detectStorageConfig(): StorageConfig;
60
-
61
- /**
62
- * File-based storage adapter
63
- * Stores SEO records in a JSON file
64
- * No database required!
65
- */
66
-
67
- declare class FileStorage implements StorageAdapter {
68
- private filePath;
69
- private records;
70
- private initialized;
71
- constructor(filePath?: string);
72
- private ensureInitialized;
73
- private save;
74
- isAvailable(): Promise<boolean>;
75
- getRecords(): Promise<SEORecord[]>;
76
- getRecordById(id: string): Promise<SEORecord | null>;
77
- getRecordByRoute(routePath: string): Promise<SEORecord | null>;
78
- createRecord(record: CreateSEORecord): Promise<SEORecord>;
79
- updateRecord(record: UpdateSEORecord): Promise<SEORecord>;
80
- deleteRecord(id: string): Promise<void>;
81
- }
82
-
83
- export { CreateSEORecord, FileStorage, SEORecord, type StorageAdapter, type StorageConfig, type StorageType, UpdateSEORecord, createStorageAdapter, detectStorageConfig };
package/dist/index.js CHANGED
@@ -37,22 +37,16 @@ __export(index_exports, {
37
37
  CardFooter: () => CardFooter,
38
38
  CardHeader: () => CardHeader,
39
39
  CardTitle: () => CardTitle,
40
- FileStorage: () => FileStorage,
41
40
  Input: () => Input,
42
41
  OGImagePreview: () => OGImagePreview,
43
42
  SEORecordForm: () => SEORecordForm,
44
43
  SEORecordList: () => SEORecordList,
45
44
  Spinner: () => Spinner,
46
45
  ValidationDashboard: () => ValidationDashboard,
47
- crawlSiteForSEO: () => crawlSiteForSEO,
48
46
  createSEORecordSchema: () => createSEORecordSchema,
49
- createStorageAdapter: () => createStorageAdapter,
50
47
  detectStorageConfig: () => detectStorageConfig,
51
- discoverNextJSRoutes: () => discoverNextJSRoutes,
52
48
  extractMetadataFromHTML: () => extractMetadataFromHTML,
53
- extractMetadataFromURL: () => extractMetadataFromURL,
54
49
  extractSitemapFromRobotsTxt: () => extractSitemapFromRobotsTxt,
55
- generateExamplePaths: () => generateExamplePaths,
56
50
  generateRobotsTxt: () => generateRobotsTxt,
57
51
  generateSitemapFromRecords: () => generateSitemapFromRecords,
58
52
  generateSitemapXML: () => generateSitemapXML,
@@ -3644,82 +3638,6 @@ function ValidationDashboard() {
3644
3638
  ] });
3645
3639
  }
3646
3640
 
3647
- // src/lib/route-discovery.ts
3648
- var import_path = require("path");
3649
- var import_glob = require("glob");
3650
- async function discoverNextJSRoutes(appDir = "app", rootDir = process.cwd()) {
3651
- const routes = [];
3652
- const appPath = (0, import_path.join)(rootDir, appDir);
3653
- try {
3654
- const pageFiles = await (0, import_glob.glob)("**/page.tsx", {
3655
- cwd: appPath,
3656
- absolute: false,
3657
- ignore: ["**/node_modules/**", "**/.next/**"]
3658
- });
3659
- for (const file of pageFiles) {
3660
- const route = fileToRoute(file, appDir);
3661
- if (route) {
3662
- routes.push(route);
3663
- }
3664
- }
3665
- } catch (error) {
3666
- console.error("Error discovering routes:", error);
3667
- }
3668
- return routes;
3669
- }
3670
- function fileToRoute(filePath, appDir) {
3671
- let routePath = filePath.replace(/^app\//, "").replace(/\/page\.tsx$/, "").replace(/\/page$/, "");
3672
- if (routePath === "page" || routePath === "") {
3673
- routePath = "/";
3674
- } else {
3675
- routePath = "/" + routePath;
3676
- }
3677
- const segments = routePath.split("/").filter(Boolean);
3678
- const params = [];
3679
- let isDynamic = false;
3680
- let isCatchAll = false;
3681
- for (const segment of segments) {
3682
- if (segment.startsWith("[...") && segment.endsWith("]")) {
3683
- const param = segment.slice(4, -1);
3684
- params.push(param);
3685
- isDynamic = true;
3686
- isCatchAll = true;
3687
- } else if (segment.startsWith("[") && segment.endsWith("]")) {
3688
- const param = segment.slice(1, -1);
3689
- params.push(param);
3690
- isDynamic = true;
3691
- }
3692
- }
3693
- return {
3694
- routePath,
3695
- filePath: (0, import_path.join)(appDir, filePath),
3696
- isDynamic,
3697
- isCatchAll,
3698
- params
3699
- };
3700
- }
3701
- function generateExamplePaths(route, count = 3) {
3702
- if (!route.isDynamic) {
3703
- return [route.routePath];
3704
- }
3705
- const examples = [];
3706
- const segments = route.routePath.split("/").filter(Boolean);
3707
- for (let i = 0; i < count; i++) {
3708
- let examplePath = "";
3709
- for (const segment of segments) {
3710
- if (segment.startsWith("[...")) {
3711
- examplePath += `/example-${i}-part-1/example-${i}-part-2`;
3712
- } else if (segment.startsWith("[")) {
3713
- examplePath += `/example-${i}`;
3714
- } else {
3715
- examplePath += `/${segment}`;
3716
- }
3717
- }
3718
- examples.push(examplePath || "/");
3719
- }
3720
- return examples;
3721
- }
3722
-
3723
3641
  // src/lib/metadata-extractor.ts
3724
3642
  var cheerio = __toESM(require("cheerio"));
3725
3643
  function extractMetadataFromHTML(html, baseUrl) {
@@ -3748,23 +3666,6 @@ function extractMetadataFromHTML(html, baseUrl) {
3748
3666
  }
3749
3667
  return metadata;
3750
3668
  }
3751
- async function extractMetadataFromURL(url) {
3752
- try {
3753
- const response = await fetch(url, {
3754
- headers: {
3755
- "User-Agent": "SEO-Console/1.0"
3756
- }
3757
- });
3758
- if (!response.ok) {
3759
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
3760
- }
3761
- const html = await response.text();
3762
- return extractMetadataFromHTML(html, url);
3763
- } catch (error) {
3764
- console.error(`Error extracting metadata from ${url}:`, error);
3765
- return {};
3766
- }
3767
- }
3768
3669
  function metadataToSEORecord(metadata, routePath, userId = "extracted") {
3769
3670
  return {
3770
3671
  userId,
@@ -3782,20 +3683,6 @@ function metadataToSEORecord(metadata, routePath, userId = "extracted") {
3782
3683
  validationStatus: "pending"
3783
3684
  };
3784
3685
  }
3785
- async function crawlSiteForSEO(baseUrl, routes) {
3786
- const results = /* @__PURE__ */ new Map();
3787
- for (const route of routes) {
3788
- const url = new URL(route, baseUrl).toString();
3789
- try {
3790
- const metadata = await extractMetadataFromURL(url);
3791
- results.set(route, metadata);
3792
- await new Promise((resolve) => setTimeout(resolve, 100));
3793
- } catch (error) {
3794
- console.error(`Failed to crawl ${url}:`, error);
3795
- }
3796
- }
3797
- return results;
3798
- }
3799
3686
 
3800
3687
  // src/lib/sitemap-generator.ts
3801
3688
  function generateSitemapXML(options) {
@@ -3937,119 +3824,8 @@ function extractSitemapFromRobotsTxt(content) {
3937
3824
 
3938
3825
  // src/lib/storage/file-storage.ts
3939
3826
  var import_fs = require("fs");
3940
- var FileStorage = class {
3941
- constructor(filePath = "seo-records.json") {
3942
- this.records = [];
3943
- this.initialized = false;
3944
- this.filePath = filePath;
3945
- }
3946
- async ensureInitialized() {
3947
- if (this.initialized) return;
3948
- try {
3949
- const data = await import_fs.promises.readFile(this.filePath, "utf-8");
3950
- this.records = JSON.parse(data);
3951
- } catch (error) {
3952
- if (error.code === "ENOENT") {
3953
- this.records = [];
3954
- await this.save();
3955
- } else {
3956
- throw error;
3957
- }
3958
- }
3959
- this.initialized = true;
3960
- }
3961
- async save() {
3962
- await import_fs.promises.writeFile(this.filePath, JSON.stringify(this.records, null, 2), "utf-8");
3963
- }
3964
- async isAvailable() {
3965
- try {
3966
- const dir = this.filePath.includes("/") ? this.filePath.substring(0, this.filePath.lastIndexOf("/")) : ".";
3967
- await import_fs.promises.access(dir);
3968
- return true;
3969
- } catch {
3970
- return false;
3971
- }
3972
- }
3973
- async getRecords() {
3974
- await this.ensureInitialized();
3975
- return [...this.records];
3976
- }
3977
- async getRecordById(id) {
3978
- await this.ensureInitialized();
3979
- return this.records.find((r) => r.id === id) || null;
3980
- }
3981
- async getRecordByRoute(routePath) {
3982
- await this.ensureInitialized();
3983
- return this.records.find((r) => r.routePath === routePath) || null;
3984
- }
3985
- async createRecord(record) {
3986
- await this.ensureInitialized();
3987
- const newRecord = {
3988
- id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
3989
- userId: "file-user",
3990
- // File storage doesn't need user IDs
3991
- routePath: record.routePath,
3992
- title: record.title,
3993
- description: record.description,
3994
- keywords: record.keywords,
3995
- ogTitle: record.ogTitle,
3996
- ogDescription: record.ogDescription,
3997
- ogImageUrl: record.ogImageUrl,
3998
- ogImageWidth: record.ogImageWidth,
3999
- ogImageHeight: record.ogImageHeight,
4000
- ogType: record.ogType,
4001
- ogUrl: record.ogUrl,
4002
- ogSiteName: record.ogSiteName,
4003
- twitterCard: record.twitterCard,
4004
- twitterTitle: record.twitterTitle,
4005
- twitterDescription: record.twitterDescription,
4006
- twitterImageUrl: record.twitterImageUrl,
4007
- twitterSite: record.twitterSite,
4008
- twitterCreator: record.twitterCreator,
4009
- canonicalUrl: record.canonicalUrl,
4010
- robots: record.robots,
4011
- author: record.author,
4012
- publishedTime: record.publishedTime,
4013
- modifiedTime: record.modifiedTime,
4014
- structuredData: record.structuredData,
4015
- validationStatus: "pending",
4016
- lastValidatedAt: void 0,
4017
- validationErrors: void 0
4018
- };
4019
- this.records.push(newRecord);
4020
- await this.save();
4021
- return newRecord;
4022
- }
4023
- async updateRecord(record) {
4024
- await this.ensureInitialized();
4025
- const index = this.records.findIndex((r) => r.id === record.id);
4026
- if (index === -1) {
4027
- throw new Error(`SEO record with id ${record.id} not found`);
4028
- }
4029
- const updated = {
4030
- ...this.records[index],
4031
- ...record
4032
- };
4033
- this.records[index] = updated;
4034
- await this.save();
4035
- return updated;
4036
- }
4037
- async deleteRecord(id) {
4038
- await this.ensureInitialized();
4039
- const index = this.records.findIndex((r) => r.id === id);
4040
- if (index === -1) {
4041
- throw new Error(`SEO record with id ${id} not found`);
4042
- }
4043
- this.records.splice(index, 1);
4044
- await this.save();
4045
- }
4046
- };
4047
3827
 
4048
3828
  // src/lib/storage/storage-factory.ts
4049
- function createStorageAdapter(config) {
4050
- const filePath = config?.filePath || process.env.SEO_CONSOLE_STORAGE_PATH || "seo-records.json";
4051
- return new FileStorage(filePath);
4052
- }
4053
3829
  function detectStorageConfig() {
4054
3830
  return {
4055
3831
  type: "file",
@@ -4065,22 +3841,16 @@ function detectStorageConfig() {
4065
3841
  CardFooter,
4066
3842
  CardHeader,
4067
3843
  CardTitle,
4068
- FileStorage,
4069
3844
  Input,
4070
3845
  OGImagePreview,
4071
3846
  SEORecordForm,
4072
3847
  SEORecordList,
4073
3848
  Spinner,
4074
3849
  ValidationDashboard,
4075
- crawlSiteForSEO,
4076
3850
  createSEORecordSchema,
4077
- createStorageAdapter,
4078
3851
  detectStorageConfig,
4079
- discoverNextJSRoutes,
4080
3852
  extractMetadataFromHTML,
4081
- extractMetadataFromURL,
4082
3853
  extractSitemapFromRobotsTxt,
4083
- generateExamplePaths,
4084
3854
  generateRobotsTxt,
4085
3855
  generateSitemapFromRecords,
4086
3856
  generateSitemapXML,