@orion-studios/payload-seo-audit 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.
- package/README.md +127 -0
- package/bin/init.js +267 -0
- package/dist/api/backlinks-import.d.ts +4 -0
- package/dist/api/backlinks-import.d.ts.map +1 -0
- package/dist/api/backlinks-import.js +182 -0
- package/dist/api/cron.d.ts +4 -0
- package/dist/api/cron.d.ts.map +1 -0
- package/dist/api/cron.js +89 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +21 -0
- package/dist/api/page-result.d.ts +4 -0
- package/dist/api/page-result.d.ts.map +1 -0
- package/dist/api/page-result.js +93 -0
- package/dist/api/page-results.d.ts +4 -0
- package/dist/api/page-results.d.ts.map +1 -0
- package/dist/api/page-results.js +83 -0
- package/dist/api/run-stream.d.ts +4 -0
- package/dist/api/run-stream.d.ts.map +1 -0
- package/dist/api/run-stream.js +273 -0
- package/dist/api/run.d.ts +4 -0
- package/dist/api/run.d.ts.map +1 -0
- package/dist/api/run.js +102 -0
- package/dist/api/snapshot-report.d.ts +4 -0
- package/dist/api/snapshot-report.d.ts.map +1 -0
- package/dist/api/snapshot-report.js +130 -0
- package/dist/api/snapshots.d.ts +4 -0
- package/dist/api/snapshots.d.ts.map +1 -0
- package/dist/api/snapshots.js +138 -0
- package/dist/api/trend.d.ts +4 -0
- package/dist/api/trend.d.ts.map +1 -0
- package/dist/api/trend.js +71 -0
- package/dist/collections/SeoAuthoritySnapshots.d.ts +3 -0
- package/dist/collections/SeoAuthoritySnapshots.d.ts.map +1 -0
- package/dist/collections/SeoAuthoritySnapshots.js +83 -0
- package/dist/collections/SeoKeywordVisibility.d.ts +3 -0
- package/dist/collections/SeoKeywordVisibility.d.ts.map +1 -0
- package/dist/collections/SeoKeywordVisibility.js +65 -0
- package/dist/collections/SeoPageResults.d.ts +3 -0
- package/dist/collections/SeoPageResults.d.ts.map +1 -0
- package/dist/collections/SeoPageResults.js +170 -0
- package/dist/collections/SeoSnapshots.d.ts +3 -0
- package/dist/collections/SeoSnapshots.d.ts.map +1 -0
- package/dist/collections/SeoSnapshots.js +131 -0
- package/dist/components/hooks/useSeoApi.d.ts +7 -0
- package/dist/components/hooks/useSeoApi.d.ts.map +1 -0
- package/dist/components/hooks/useSeoApi.js +31 -0
- package/dist/components/hooks/useSeoPageResults.d.ts +19 -0
- package/dist/components/hooks/useSeoPageResults.d.ts.map +1 -0
- package/dist/components/hooks/useSeoPageResults.js +62 -0
- package/dist/components/hooks/useSeoSnapshot.d.ts +8 -0
- package/dist/components/hooks/useSeoSnapshot.d.ts.map +1 -0
- package/dist/components/hooks/useSeoSnapshot.js +39 -0
- package/dist/components/hooks/useSeoTrend.d.ts +8 -0
- package/dist/components/hooks/useSeoTrend.d.ts.map +1 -0
- package/dist/components/hooks/useSeoTrend.js +38 -0
- package/dist/components/layout/SeoReportHeader.d.ts +10 -0
- package/dist/components/layout/SeoReportHeader.d.ts.map +1 -0
- package/dist/components/layout/SeoReportHeader.js +18 -0
- package/dist/components/layout/SeoReportShell.d.ts +9 -0
- package/dist/components/layout/SeoReportShell.d.ts.map +1 -0
- package/dist/components/layout/SeoReportShell.js +17 -0
- package/dist/components/pdf/PdfDownloadButton.d.ts +9 -0
- package/dist/components/pdf/PdfDownloadButton.d.ts.map +1 -0
- package/dist/components/pdf/PdfDownloadButton.js +80 -0
- package/dist/components/tables/IssueTable.d.ts +11 -0
- package/dist/components/tables/IssueTable.d.ts.map +1 -0
- package/dist/components/tables/IssueTable.js +121 -0
- package/dist/components/tables/PageResultsTable.d.ts +18 -0
- package/dist/components/tables/PageResultsTable.d.ts.map +1 -0
- package/dist/components/tables/PageResultsTable.js +96 -0
- package/dist/components/types.d.ts +107 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +22 -0
- package/dist/components/utils/formatters.d.ts +15 -0
- package/dist/components/utils/formatters.d.ts.map +1 -0
- package/dist/components/utils/formatters.js +98 -0
- package/dist/components/utils/scoreHelpers.d.ts +17 -0
- package/dist/components/utils/scoreHelpers.d.ts.map +1 -0
- package/dist/components/utils/scoreHelpers.js +139 -0
- package/dist/components/views/SeoDashboard.d.ts +3 -0
- package/dist/components/views/SeoDashboard.d.ts.map +1 -0
- package/dist/components/views/SeoDashboard.js +239 -0
- package/dist/components/views/SeoPageReport.d.ts +3 -0
- package/dist/components/views/SeoPageReport.d.ts.map +1 -0
- package/dist/components/views/SeoPageReport.js +234 -0
- package/dist/components/views/SeoSnapshotReport.d.ts +3 -0
- package/dist/components/views/SeoSnapshotReport.d.ts.map +1 -0
- package/dist/components/views/SeoSnapshotReport.js +224 -0
- package/dist/components/visualization/CategoryScoreCard.d.ts +11 -0
- package/dist/components/visualization/CategoryScoreCard.d.ts.map +1 -0
- package/dist/components/visualization/CategoryScoreCard.js +17 -0
- package/dist/components/visualization/CategoryScoreGrid.d.ts +9 -0
- package/dist/components/visualization/CategoryScoreGrid.d.ts.map +1 -0
- package/dist/components/visualization/CategoryScoreGrid.js +32 -0
- package/dist/components/visualization/IssueCategoryChart.d.ts +8 -0
- package/dist/components/visualization/IssueCategoryChart.d.ts.map +1 -0
- package/dist/components/visualization/IssueCategoryChart.js +47 -0
- package/dist/components/visualization/MetricCard.d.ts +11 -0
- package/dist/components/visualization/MetricCard.d.ts.map +1 -0
- package/dist/components/visualization/MetricCard.js +17 -0
- package/dist/components/visualization/MetricCardRow.d.ts +7 -0
- package/dist/components/visualization/MetricCardRow.d.ts.map +1 -0
- package/dist/components/visualization/MetricCardRow.js +12 -0
- package/dist/components/visualization/ScoreBar.d.ts +11 -0
- package/dist/components/visualization/ScoreBar.d.ts.map +1 -0
- package/dist/components/visualization/ScoreBar.js +34 -0
- package/dist/components/visualization/ScoreGauge.d.ts +11 -0
- package/dist/components/visualization/ScoreGauge.d.ts.map +1 -0
- package/dist/components/visualization/ScoreGauge.js +28 -0
- package/dist/components/visualization/ScoreTrendChart.d.ts +9 -0
- package/dist/components/visualization/ScoreTrendChart.d.ts.map +1 -0
- package/dist/components/visualization/ScoreTrendChart.js +43 -0
- package/dist/components/visualization/SeverityBadge.d.ts +8 -0
- package/dist/components/visualization/SeverityBadge.d.ts.map +1 -0
- package/dist/components/visualization/SeverityBadge.js +14 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/exports/components.d.ts +4 -0
- package/dist/exports/components.d.ts.map +1 -0
- package/dist/exports/components.js +9 -0
- package/dist/globals/SeoDashboard.d.ts +3 -0
- package/dist/globals/SeoDashboard.d.ts.map +1 -0
- package/dist/globals/SeoDashboard.js +25 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/utilities/access.d.ts +8 -0
- package/dist/utilities/access.d.ts.map +1 -0
- package/dist/utilities/access.js +11 -0
- package/dist/utilities/auth.d.ts +7 -0
- package/dist/utilities/auth.d.ts.map +1 -0
- package/dist/utilities/auth.js +28 -0
- package/dist/utilities/checks.d.ts +3 -0
- package/dist/utilities/checks.d.ts.map +1 -0
- package/dist/utilities/checks.js +255 -0
- package/dist/utilities/crawler.d.ts +14 -0
- package/dist/utilities/crawler.d.ts.map +1 -0
- package/dist/utilities/crawler.js +152 -0
- package/dist/utilities/gsc.d.ts +15 -0
- package/dist/utilities/gsc.d.ts.map +1 -0
- package/dist/utilities/gsc.js +69 -0
- package/dist/utilities/helpers.d.ts +7 -0
- package/dist/utilities/helpers.d.ts.map +1 -0
- package/dist/utilities/helpers.js +44 -0
- package/dist/utilities/pagespeed.d.ts +3 -0
- package/dist/utilities/pagespeed.d.ts.map +1 -0
- package/dist/utilities/pagespeed.js +49 -0
- package/dist/utilities/providers.d.ts +3 -0
- package/dist/utilities/providers.d.ts.map +1 -0
- package/dist/utilities/providers.js +18 -0
- package/dist/utilities/runAudit.d.ts +14 -0
- package/dist/utilities/runAudit.d.ts.map +1 -0
- package/dist/utilities/runAudit.js +224 -0
- package/dist/utilities/scoring.d.ts +3 -0
- package/dist/utilities/scoring.d.ts.map +1 -0
- package/dist/utilities/scoring.js +45 -0
- package/dist/utilities/triggers.d.ts +3 -0
- package/dist/utilities/triggers.d.ts.map +1 -0
- package/dist/utilities/triggers.js +39 -0
- package/dist/utilities/types.d.ts +87 -0
- package/dist/utilities/types.d.ts.map +1 -0
- package/dist/utilities/types.js +2 -0
- package/package.json +63 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type SeoAuditPluginConfig = {
|
|
2
|
+
site: {
|
|
3
|
+
name: string;
|
|
4
|
+
domain: string;
|
|
5
|
+
canonicalHost: string;
|
|
6
|
+
sitemapURL?: string;
|
|
7
|
+
keyURLs?: string[];
|
|
8
|
+
};
|
|
9
|
+
crawl?: {
|
|
10
|
+
maxPages?: number;
|
|
11
|
+
maxDepth?: number;
|
|
12
|
+
requestTimeoutMs?: number;
|
|
13
|
+
includePatterns?: string[];
|
|
14
|
+
excludePatterns?: string[];
|
|
15
|
+
};
|
|
16
|
+
triggers?: {
|
|
17
|
+
collections?: string[];
|
|
18
|
+
globals?: string[];
|
|
19
|
+
};
|
|
20
|
+
access?: (user: any) => boolean;
|
|
21
|
+
integrations?: {
|
|
22
|
+
enablePageSpeed?: boolean;
|
|
23
|
+
enableSearchConsole?: boolean;
|
|
24
|
+
};
|
|
25
|
+
thresholds?: {
|
|
26
|
+
targetLCPMs?: number;
|
|
27
|
+
targetCLS?: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export type NormalizedSeoConfig = Required<Omit<SeoAuditPluginConfig, 'site'>> & {
|
|
31
|
+
site: Required<SeoAuditPluginConfig['site']>;
|
|
32
|
+
crawl: Required<NonNullable<SeoAuditPluginConfig['crawl']>>;
|
|
33
|
+
triggers: Required<NonNullable<SeoAuditPluginConfig['triggers']>>;
|
|
34
|
+
integrations: Required<NonNullable<SeoAuditPluginConfig['integrations']>>;
|
|
35
|
+
thresholds: Required<NonNullable<SeoAuditPluginConfig['thresholds']>>;
|
|
36
|
+
};
|
|
37
|
+
export declare function normalizeConfig(config: SeoAuditPluginConfig): NormalizedSeoConfig;
|
|
38
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,oBAAoB,GAAG;IAIjC,IAAI,EAAE;QAEJ,IAAI,EAAE,MAAM,CAAA;QAEZ,MAAM,EAAE,MAAM,CAAA;QAEd,aAAa,EAAE,MAAM,CAAA;QAErB,UAAU,CAAC,EAAE,MAAM,CAAA;QAEnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KACnB,CAAA;IAKD,KAAK,CAAC,EAAE;QAEN,QAAQ,CAAC,EAAE,MAAM,CAAA;QAEjB,QAAQ,CAAC,EAAE,MAAM,CAAA;QAEjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;QAEzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;QAE1B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAC3B,CAAA;IAKD,QAAQ,CAAC,EAAE;QAET,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;QAEtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KACnB,CAAA;IAQD,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAA;IAK/B,YAAY,CAAC,EAAE;QAEb,eAAe,CAAC,EAAE,OAAO,CAAA;QAEzB,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAC9B,CAAA;IAKD,UAAU,CAAC,EAAE;QAEX,WAAW,CAAC,EAAE,MAAM,CAAA;QAEpB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF,CAAA;AAKD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,GAAG;IAC/E,IAAI,EAAE,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5C,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IACjE,YAAY,EAAE,QAAQ,CAAC,WAAW,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAA;IACzE,UAAU,EAAE,QAAQ,CAAC,WAAW,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;CACtE,CAAA;AAKD,wBAAgB,eAAe,CAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAiCjF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeConfig = normalizeConfig;
|
|
4
|
+
function normalizeConfig(config) {
|
|
5
|
+
const sitemapURL = config.site.sitemapURL || `${config.site.canonicalHost}/sitemap.xml`;
|
|
6
|
+
const keyURLs = config.site.keyURLs || [config.site.canonicalHost];
|
|
7
|
+
return {
|
|
8
|
+
site: {
|
|
9
|
+
name: config.site.name,
|
|
10
|
+
domain: config.site.domain,
|
|
11
|
+
canonicalHost: config.site.canonicalHost,
|
|
12
|
+
sitemapURL,
|
|
13
|
+
keyURLs,
|
|
14
|
+
},
|
|
15
|
+
crawl: {
|
|
16
|
+
maxPages: config.crawl?.maxPages ?? 120,
|
|
17
|
+
maxDepth: config.crawl?.maxDepth ?? 2,
|
|
18
|
+
requestTimeoutMs: config.crawl?.requestTimeoutMs ?? 10000,
|
|
19
|
+
includePatterns: config.crawl?.includePatterns ?? [],
|
|
20
|
+
excludePatterns: config.crawl?.excludePatterns ?? ['/admin', '/api', '?preview='],
|
|
21
|
+
},
|
|
22
|
+
triggers: {
|
|
23
|
+
collections: config.triggers?.collections ?? [],
|
|
24
|
+
globals: config.triggers?.globals ?? [],
|
|
25
|
+
},
|
|
26
|
+
access: config.access ?? ((user) => user?.role === 'admin'),
|
|
27
|
+
integrations: {
|
|
28
|
+
enablePageSpeed: config.integrations?.enablePageSpeed ?? true,
|
|
29
|
+
enableSearchConsole: config.integrations?.enableSearchConsole ?? false,
|
|
30
|
+
},
|
|
31
|
+
thresholds: {
|
|
32
|
+
targetLCPMs: config.thresholds?.targetLCPMs ?? 2500,
|
|
33
|
+
targetCLS: config.thresholds?.targetCLS ?? 0.1,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/exports/components.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SeoPageReport = exports.SeoSnapshotReport = exports.SeoDashboard = void 0;
|
|
4
|
+
var SeoDashboard_1 = require("../components/views/SeoDashboard");
|
|
5
|
+
Object.defineProperty(exports, "SeoDashboard", { enumerable: true, get: function () { return SeoDashboard_1.SeoDashboard; } });
|
|
6
|
+
var SeoSnapshotReport_1 = require("../components/views/SeoSnapshotReport");
|
|
7
|
+
Object.defineProperty(exports, "SeoSnapshotReport", { enumerable: true, get: function () { return SeoSnapshotReport_1.SeoSnapshotReport; } });
|
|
8
|
+
var SeoPageReport_1 = require("../components/views/SeoPageReport");
|
|
9
|
+
Object.defineProperty(exports, "SeoPageReport", { enumerable: true, get: function () { return SeoPageReport_1.SeoPageReport; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoDashboard.d.ts","sourceRoot":"","sources":["../../src/globals/SeoDashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAG3C,eAAO,MAAM,YAAY,EAAE,YAoB1B,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SeoDashboard = void 0;
|
|
4
|
+
const access_1 = require("../utilities/access");
|
|
5
|
+
exports.SeoDashboard = {
|
|
6
|
+
slug: 'seo-dashboard',
|
|
7
|
+
label: 'SEO Dashboard',
|
|
8
|
+
admin: {
|
|
9
|
+
group: 'SEO',
|
|
10
|
+
components: {
|
|
11
|
+
views: {
|
|
12
|
+
edit: {
|
|
13
|
+
root: {
|
|
14
|
+
Component: '@orion-studios/payload-seo-audit/components#SeoDashboard',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
access: {
|
|
21
|
+
read: access_1.seoAdminAccess,
|
|
22
|
+
update: () => false,
|
|
23
|
+
},
|
|
24
|
+
fields: [],
|
|
25
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
import type { SeoAuditPluginConfig, NormalizedSeoConfig } from './config';
|
|
3
|
+
export type { SeoAuditPluginConfig } from './config';
|
|
4
|
+
export declare const seoAuditPlugin: (pluginConfig: SeoAuditPluginConfig) => (incomingConfig: Config) => Config;
|
|
5
|
+
export declare function getSeoConfig(payload: any): NormalizedSeoConfig | null;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAazE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAsCpD,eAAO,MAAM,cAAc,GAAI,cAAc,oBAAoB,MACvD,gBAAgB,MAAM,KAAG,MA+BlC,CAAA;AAMD,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,mBAAmB,GAAG,IAAI,CAErE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.seoAuditPlugin = void 0;
|
|
4
|
+
exports.getSeoConfig = getSeoConfig;
|
|
5
|
+
const config_1 = require("./config");
|
|
6
|
+
const SeoSnapshots_1 = require("./collections/SeoSnapshots");
|
|
7
|
+
const SeoPageResults_1 = require("./collections/SeoPageResults");
|
|
8
|
+
const SeoKeywordVisibility_1 = require("./collections/SeoKeywordVisibility");
|
|
9
|
+
const SeoAuthoritySnapshots_1 = require("./collections/SeoAuthoritySnapshots");
|
|
10
|
+
const SeoDashboard_1 = require("./globals/SeoDashboard");
|
|
11
|
+
const seoAuditPlugin = (pluginConfig) => {
|
|
12
|
+
return (incomingConfig) => {
|
|
13
|
+
const seoConfig = (0, config_1.normalizeConfig)(pluginConfig);
|
|
14
|
+
const collections = [
|
|
15
|
+
...(incomingConfig.collections || []),
|
|
16
|
+
SeoSnapshots_1.SeoSnapshots,
|
|
17
|
+
SeoPageResults_1.SeoPageResults,
|
|
18
|
+
SeoKeywordVisibility_1.SeoKeywordVisibility,
|
|
19
|
+
SeoAuthoritySnapshots_1.SeoAuthoritySnapshots,
|
|
20
|
+
];
|
|
21
|
+
const globals = [
|
|
22
|
+
...(incomingConfig.globals || []),
|
|
23
|
+
SeoDashboard_1.SeoDashboard,
|
|
24
|
+
];
|
|
25
|
+
return {
|
|
26
|
+
...incomingConfig,
|
|
27
|
+
collections,
|
|
28
|
+
globals,
|
|
29
|
+
custom: {
|
|
30
|
+
...incomingConfig.custom,
|
|
31
|
+
seoAudit: seoConfig,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
exports.seoAuditPlugin = seoAuditPlugin;
|
|
37
|
+
function getSeoConfig(payload) {
|
|
38
|
+
return payload.config?.custom?.seoAudit || null;
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Access } from 'payload';
|
|
2
|
+
type AdminUserLike = {
|
|
3
|
+
role?: string | null;
|
|
4
|
+
} | null;
|
|
5
|
+
export declare const seoAdminAccess: Access;
|
|
6
|
+
export declare const canRunSeoAutomation: (user: AdminUserLike) => boolean;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/utilities/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,KAAK,aAAa,GAAG;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,GAAG,IAAI,CAAA;AAIR,eAAO,MAAM,cAAc,EAAE,MAG5B,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,YAAsB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canRunSeoAutomation = exports.seoAdminAccess = void 0;
|
|
4
|
+
const isAdminUser = (user) => user?.role === 'admin';
|
|
5
|
+
const seoAdminAccess = ({ req }) => {
|
|
6
|
+
const user = (req.user || null);
|
|
7
|
+
return isAdminUser(user);
|
|
8
|
+
};
|
|
9
|
+
exports.seoAdminAccess = seoAdminAccess;
|
|
10
|
+
const canRunSeoAutomation = (user) => isAdminUser(user);
|
|
11
|
+
exports.canRunSeoAutomation = canRunSeoAutomation;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
|
+
import type { Payload } from 'payload';
|
|
3
|
+
export declare const authenticateSEOAdmin: (payload: Payload, request: NextRequest) => Promise<{
|
|
4
|
+
role?: string | null;
|
|
5
|
+
} | null>;
|
|
6
|
+
export declare const isValidSEOSecret: (request: NextRequest) => boolean;
|
|
7
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utilities/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,eAAO,MAAM,oBAAoB,GAAU,SAAS,OAAO,EAAE,SAAS,WAAW;WAG7B,MAAM,GAAG,IAAI;SAMhE,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,YAWpD,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidSEOSecret = exports.authenticateSEOAdmin = void 0;
|
|
4
|
+
const access_1 = require("../utilities/access");
|
|
5
|
+
const authenticateSEOAdmin = async (payload, request) => {
|
|
6
|
+
try {
|
|
7
|
+
const result = await payload.auth({ headers: request.headers });
|
|
8
|
+
const user = (result?.user || null);
|
|
9
|
+
if (!(0, access_1.canRunSeoAutomation)(user))
|
|
10
|
+
return null;
|
|
11
|
+
return user;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
exports.authenticateSEOAdmin = authenticateSEOAdmin;
|
|
18
|
+
const isValidSEOSecret = (request) => {
|
|
19
|
+
const configuredSecret = process.env.SEO_AUDIT_SECRET;
|
|
20
|
+
if (!configuredSecret)
|
|
21
|
+
return false;
|
|
22
|
+
const headerSecret = request.headers.get('x-seo-audit-secret') ||
|
|
23
|
+
request.headers.get('authorization')?.replace(/^Bearer\s+/i, '');
|
|
24
|
+
const querySecret = new URL(request.url).searchParams.get('secret');
|
|
25
|
+
const bodySecret = request.headers.get('x-cron-secret');
|
|
26
|
+
return [headerSecret, querySecret, bodySecret].some((value) => value === configuredSecret);
|
|
27
|
+
};
|
|
28
|
+
exports.isValidSEOSecret = isValidSEOSecret;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/utilities/checks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAgD,MAAM,oBAAoB,CAAA;AAqGtG,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,KAAG,cA2LxD,CAAA"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSEOChecks = void 0;
|
|
4
|
+
const helpers_1 = require("../utilities/helpers");
|
|
5
|
+
const getTagContent = (html, pattern) => {
|
|
6
|
+
const match = html.match(pattern);
|
|
7
|
+
return match?.[1]?.trim();
|
|
8
|
+
};
|
|
9
|
+
const stripHtml = (html) => html
|
|
10
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
11
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
12
|
+
.replace(/<[^>]+>/g, ' ')
|
|
13
|
+
.replace(/\s+/g, ' ')
|
|
14
|
+
.trim();
|
|
15
|
+
const extractJsonLdBlocks = (html) => [...html.matchAll(/<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)].map((match) => match[1]?.trim() || '');
|
|
16
|
+
const getHeadingLevels = (html) => [...html.matchAll(/<h([1-6])\b[^>]*>/gi)]
|
|
17
|
+
.map((match) => Number(match[1]))
|
|
18
|
+
.filter((value) => Number.isInteger(value));
|
|
19
|
+
const countAnchors = (html, baseOrigin) => {
|
|
20
|
+
const links = [...html.matchAll(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi)]
|
|
21
|
+
.map((match) => match[1]?.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
let internal = 0;
|
|
24
|
+
let external = 0;
|
|
25
|
+
links.forEach((href) => {
|
|
26
|
+
if (href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:'))
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(href, baseOrigin);
|
|
30
|
+
if (parsed.origin === baseOrigin)
|
|
31
|
+
internal += 1;
|
|
32
|
+
else
|
|
33
|
+
external += 1;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
internal += 1;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return { internal, external };
|
|
40
|
+
};
|
|
41
|
+
const countImageAltGaps = (html) => {
|
|
42
|
+
const imageTags = [...html.matchAll(/<img\b[^>]*>/gi)].map((match) => match[0]);
|
|
43
|
+
let missingAlt = 0;
|
|
44
|
+
imageTags.forEach((tag) => {
|
|
45
|
+
const altMatch = tag.match(/\salt\s*=\s*["']([^"']*)["']/i);
|
|
46
|
+
if (!altMatch) {
|
|
47
|
+
missingAlt += 1;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return { imagesTotal: imageTags.length, imagesMissingAlt: missingAlt };
|
|
51
|
+
};
|
|
52
|
+
const countHeadingOrderIssues = (levels) => {
|
|
53
|
+
if (levels.length <= 1)
|
|
54
|
+
return 0;
|
|
55
|
+
let issues = 0;
|
|
56
|
+
for (let i = 1; i < levels.length; i += 1) {
|
|
57
|
+
const previous = levels[i - 1];
|
|
58
|
+
const current = levels[i];
|
|
59
|
+
if (current - previous > 1)
|
|
60
|
+
issues += 1;
|
|
61
|
+
}
|
|
62
|
+
return issues;
|
|
63
|
+
};
|
|
64
|
+
const pushIssue = (issues, { url, ruleID, category, severity, message, recommendation, }) => {
|
|
65
|
+
issues.push({
|
|
66
|
+
fingerprint: (0, helpers_1.createIssueFingerprint)(url, ruleID, message),
|
|
67
|
+
ruleID,
|
|
68
|
+
category,
|
|
69
|
+
severity,
|
|
70
|
+
message,
|
|
71
|
+
recommendation,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const runSEOChecks = (url, html) => {
|
|
75
|
+
const issues = [];
|
|
76
|
+
const origin = new URL(url).origin;
|
|
77
|
+
const title = getTagContent(html, /<title[^>]*>([^<]+)<\/title>/i);
|
|
78
|
+
const metaDescription = getTagContent(html, /<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["'][^>]*>/i);
|
|
79
|
+
const canonical = getTagContent(html, /<link[^>]*rel=["']canonical["'][^>]*href=["']([^"']+)["'][^>]*>/i);
|
|
80
|
+
const robotsMeta = getTagContent(html, /<meta[^>]*name=["']robots["'][^>]*content=["']([^"']+)["'][^>]*>/i);
|
|
81
|
+
const h1Count = (html.match(/<h1\b[^>]*>/gi) || []).length;
|
|
82
|
+
const headingLevels = getHeadingLevels(html);
|
|
83
|
+
const headingOrderIssues = countHeadingOrderIssues(headingLevels);
|
|
84
|
+
const { internal, external } = countAnchors(html, origin);
|
|
85
|
+
const { imagesTotal, imagesMissingAlt } = countImageAltGaps(html);
|
|
86
|
+
const structuredDataBlocks = extractJsonLdBlocks(html);
|
|
87
|
+
if (!title) {
|
|
88
|
+
pushIssue(issues, {
|
|
89
|
+
url,
|
|
90
|
+
ruleID: 'title-missing',
|
|
91
|
+
category: 'metadata',
|
|
92
|
+
severity: 'critical',
|
|
93
|
+
message: 'Page is missing a title tag.',
|
|
94
|
+
recommendation: 'Add a unique, descriptive <title> tag for this page.',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (title.length < 20 || title.length > 65) {
|
|
98
|
+
pushIssue(issues, {
|
|
99
|
+
url,
|
|
100
|
+
ruleID: 'title-length',
|
|
101
|
+
category: 'metadata',
|
|
102
|
+
severity: 'medium',
|
|
103
|
+
message: `Title length is ${title.length} characters.`,
|
|
104
|
+
recommendation: 'Keep titles between 20 and 65 characters where possible.',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (!metaDescription) {
|
|
108
|
+
pushIssue(issues, {
|
|
109
|
+
url,
|
|
110
|
+
ruleID: 'meta-description-missing',
|
|
111
|
+
category: 'metadata',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
message: 'Page is missing a meta description.',
|
|
114
|
+
recommendation: 'Add a compelling meta description around 120-160 characters.',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else if (metaDescription.length < 70 || metaDescription.length > 165) {
|
|
118
|
+
pushIssue(issues, {
|
|
119
|
+
url,
|
|
120
|
+
ruleID: 'meta-description-length',
|
|
121
|
+
category: 'metadata',
|
|
122
|
+
severity: 'low',
|
|
123
|
+
message: `Meta description length is ${metaDescription.length} characters.`,
|
|
124
|
+
recommendation: 'Aim for roughly 120-160 characters.',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!canonical) {
|
|
128
|
+
pushIssue(issues, {
|
|
129
|
+
url,
|
|
130
|
+
ruleID: 'canonical-missing',
|
|
131
|
+
category: 'indexability',
|
|
132
|
+
severity: 'high',
|
|
133
|
+
message: 'Page is missing a canonical URL tag.',
|
|
134
|
+
recommendation: 'Add a canonical URL to avoid duplicate indexing signals.',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (robotsMeta?.toLowerCase().includes('noindex')) {
|
|
138
|
+
pushIssue(issues, {
|
|
139
|
+
url,
|
|
140
|
+
ruleID: 'robots-noindex',
|
|
141
|
+
category: 'indexability',
|
|
142
|
+
severity: 'info',
|
|
143
|
+
message: 'Page contains a noindex robots directive.',
|
|
144
|
+
recommendation: 'Confirm this URL should not be indexed.',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (h1Count === 0) {
|
|
148
|
+
pushIssue(issues, {
|
|
149
|
+
url,
|
|
150
|
+
ruleID: 'h1-missing',
|
|
151
|
+
category: 'structure',
|
|
152
|
+
severity: 'high',
|
|
153
|
+
message: 'Page is missing an H1 heading.',
|
|
154
|
+
recommendation: 'Include one clear H1 describing the primary page topic.',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else if (h1Count > 1) {
|
|
158
|
+
pushIssue(issues, {
|
|
159
|
+
url,
|
|
160
|
+
ruleID: 'h1-multiple',
|
|
161
|
+
category: 'structure',
|
|
162
|
+
severity: 'medium',
|
|
163
|
+
message: `Page has ${h1Count} H1 headings.`,
|
|
164
|
+
recommendation: 'Use one H1 and structure the rest as H2-H6.',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (headingOrderIssues > 0) {
|
|
168
|
+
pushIssue(issues, {
|
|
169
|
+
url,
|
|
170
|
+
ruleID: 'heading-order',
|
|
171
|
+
category: 'structure',
|
|
172
|
+
severity: 'low',
|
|
173
|
+
message: `Detected ${headingOrderIssues} heading-level jumps.`,
|
|
174
|
+
recommendation: 'Keep heading order sequential (for example H2 to H3).',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (internal < 2) {
|
|
178
|
+
pushIssue(issues, {
|
|
179
|
+
url,
|
|
180
|
+
ruleID: 'internal-links-low',
|
|
181
|
+
category: 'links',
|
|
182
|
+
severity: 'low',
|
|
183
|
+
message: `Only ${internal} internal links found.`,
|
|
184
|
+
recommendation: 'Add contextual internal links to relevant service and conversion pages.',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (imagesMissingAlt > 0) {
|
|
188
|
+
pushIssue(issues, {
|
|
189
|
+
url,
|
|
190
|
+
ruleID: 'image-alt-missing',
|
|
191
|
+
category: 'media',
|
|
192
|
+
severity: imagesMissingAlt > 3 ? 'high' : 'medium',
|
|
193
|
+
message: `${imagesMissingAlt} image(s) are missing alt attributes.`,
|
|
194
|
+
recommendation: 'Add descriptive alt text for meaningful images.',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (structuredDataBlocks.length === 0) {
|
|
198
|
+
pushIssue(issues, {
|
|
199
|
+
url,
|
|
200
|
+
ruleID: 'structured-data-missing',
|
|
201
|
+
category: 'structuredData',
|
|
202
|
+
severity: 'low',
|
|
203
|
+
message: 'No JSON-LD structured data detected.',
|
|
204
|
+
recommendation: 'Add schema markup relevant to the page type.',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const invalidBlocks = structuredDataBlocks.filter((block) => {
|
|
209
|
+
try {
|
|
210
|
+
JSON.parse(block);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (invalidBlocks.length > 0) {
|
|
218
|
+
pushIssue(issues, {
|
|
219
|
+
url,
|
|
220
|
+
ruleID: 'structured-data-invalid',
|
|
221
|
+
category: 'structuredData',
|
|
222
|
+
severity: 'medium',
|
|
223
|
+
message: `${invalidBlocks.length} JSON-LD block(s) could not be parsed.`,
|
|
224
|
+
recommendation: 'Validate schema JSON and fix invalid syntax.',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const bodyText = stripHtml(html);
|
|
229
|
+
const wordCount = bodyText ? bodyText.split(/\s+/).length : 0;
|
|
230
|
+
if (wordCount < 120) {
|
|
231
|
+
pushIssue(issues, {
|
|
232
|
+
url,
|
|
233
|
+
ruleID: 'content-thin',
|
|
234
|
+
category: 'structure',
|
|
235
|
+
severity: 'info',
|
|
236
|
+
message: `Very low visible word count (${wordCount}).`,
|
|
237
|
+
recommendation: 'Ensure the page has meaningful content for search intent.',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
title,
|
|
242
|
+
metaDescription,
|
|
243
|
+
canonical,
|
|
244
|
+
robotsMeta,
|
|
245
|
+
h1Count,
|
|
246
|
+
headingOrderIssues,
|
|
247
|
+
internalLinks: internal,
|
|
248
|
+
externalLinks: external,
|
|
249
|
+
imagesTotal,
|
|
250
|
+
imagesMissingAlt,
|
|
251
|
+
structuredDataBlocks: structuredDataBlocks.length,
|
|
252
|
+
issues,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
exports.runSEOChecks = runSEOChecks;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SEOSiteRecord } from '../utilities/types';
|
|
2
|
+
export type CrawledPage = {
|
|
3
|
+
url: string;
|
|
4
|
+
path: string;
|
|
5
|
+
statusCode: number;
|
|
6
|
+
html: string;
|
|
7
|
+
};
|
|
8
|
+
type CrawlResult = {
|
|
9
|
+
pages: CrawledPage[];
|
|
10
|
+
visitedCount: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const crawlSiteForSEO: (site: SEOSiteRecord) => Promise<CrawlResult>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=crawler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawler.d.ts","sourceRoot":"","sources":["../../src/utilities/crawler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGvD,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAsED,eAAO,MAAM,eAAe,GAAU,MAAM,aAAa,KAAG,OAAO,CAAC,WAAW,CAgF9E,CAAA"}
|