@ijonis/geo-lint 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * @ijonis/geo-lint Configuration Types
3
+ */
4
+ /** Content directory configuration */
5
+ interface ContentPathConfig {
6
+ /** Relative path from project root, e.g. 'content/blog' */
7
+ dir: string;
8
+ /** Content type identifier */
9
+ type: 'blog' | 'page' | 'project';
10
+ /** URL prefix for permalink derivation, e.g. '/blog/' */
11
+ urlPrefix?: string;
12
+ /** Default locale when frontmatter has no locale field */
13
+ defaultLocale?: string;
14
+ }
15
+ /** GEO-specific configuration */
16
+ interface GeoConfig {
17
+ /** Brand name for entity density rule, e.g. 'ACME Corp'. Empty = skip check */
18
+ brandName: string;
19
+ /** Primary city/location for entity density rule, e.g. 'Berlin'. Empty = skip check */
20
+ brandCity: string;
21
+ /** Path to geo-keywords markdown file, relative to project root. Empty = skip */
22
+ keywordsPath: string;
23
+ }
24
+ /** User-facing configuration (partial, with defaults applied) */
25
+ interface GeoLintUserConfig {
26
+ /** Canonical site URL, e.g. 'https://example.com' (required) */
27
+ siteUrl: string;
28
+ /** Content directory configurations */
29
+ contentPaths?: ContentPathConfig[];
30
+ /** Static routes valid for link validation (e.g. ['/about', '/contact']) */
31
+ staticRoutes?: string[];
32
+ /** Image directories to scan, relative to project root */
33
+ imageDirectories?: string[];
34
+ /** Valid blog categories (empty = skip category-invalid rule) */
35
+ categories?: string[];
36
+ /** Slugs to fully exclude from linting */
37
+ excludeSlugs?: string[];
38
+ /** Content categories to fully exclude (e.g. 'legal') */
39
+ excludeCategories?: string[];
40
+ /** GEO-specific configuration */
41
+ geo?: Partial<GeoConfig>;
42
+ /** Rule severity overrides: 'off' disables a rule */
43
+ rules?: Record<string, 'error' | 'warning' | 'off'>;
44
+ /** Threshold overrides */
45
+ thresholds?: Partial<ThresholdConfig>;
46
+ }
47
+ /** Threshold configuration for validation rules */
48
+ interface ThresholdConfig {
49
+ title: {
50
+ minLength: number;
51
+ maxLength: number;
52
+ warnLength: number;
53
+ };
54
+ description: {
55
+ minLength: number;
56
+ maxLength: number;
57
+ warnLength: number;
58
+ };
59
+ slug: {
60
+ maxLength: number;
61
+ };
62
+ content: {
63
+ minWordCount: number;
64
+ minReadabilityScore: number;
65
+ };
66
+ }
67
+ /** Fully resolved configuration with all defaults applied */
68
+ interface GeoLintConfig {
69
+ siteUrl: string;
70
+ contentPaths: ContentPathConfig[];
71
+ staticRoutes: string[];
72
+ imageDirectories: string[];
73
+ categories: string[];
74
+ excludeSlugs: string[];
75
+ excludeCategories: string[];
76
+ geo: GeoConfig;
77
+ rules: Record<string, 'error' | 'warning' | 'off'>;
78
+ thresholds: ThresholdConfig;
79
+ }
80
+
81
+ /**
82
+ * @ijonis/geo-lint Core Types
83
+ * Shared interfaces for the SEO/GEO validation system
84
+ */
85
+ type Severity = 'error' | 'warning';
86
+ type ContentType = 'blog' | 'page' | 'project';
87
+ /**
88
+ * Result of a single lint check
89
+ */
90
+ interface LintResult {
91
+ /** Relative path: blog/my-post-slug */
92
+ file: string;
93
+ /** Field or area checked (e.g., 'title', 'body', 'links') */
94
+ field: string;
95
+ /** Rule identifier (e.g., 'title-too-long', 'geo-no-question-headings') */
96
+ rule: string;
97
+ /** Whether this blocks the build */
98
+ severity: Severity;
99
+ /** Human-readable error message */
100
+ message: string;
101
+ /** Optional actionable fix suggestion */
102
+ suggestion?: string;
103
+ /** Line number in MDX file if applicable */
104
+ line?: number;
105
+ }
106
+ /**
107
+ * Parsed content item combining frontmatter metadata and raw MDX body
108
+ */
109
+ interface ContentItem {
110
+ title: string;
111
+ slug: string;
112
+ description: string;
113
+ permalink: string;
114
+ image?: string;
115
+ imageAlt?: string;
116
+ categories?: string[];
117
+ date?: string;
118
+ category?: string;
119
+ locale?: string;
120
+ translationKey?: string;
121
+ updatedAt?: string;
122
+ noindex?: boolean;
123
+ draft?: boolean;
124
+ /** Content type: blog, page, or project */
125
+ contentType: ContentType;
126
+ /** Full path to source file */
127
+ filePath: string;
128
+ /** Raw file content (frontmatter + body) */
129
+ rawContent: string;
130
+ /** Body content without frontmatter */
131
+ body: string;
132
+ }
133
+ /**
134
+ * Context passed to rules for cross-content validation
135
+ */
136
+ interface RuleContext {
137
+ /** All content items (including excluded) for link/duplicate detection */
138
+ allContent: ContentItem[];
139
+ /** Set of all valid internal URL paths */
140
+ validSlugs: Set<string>;
141
+ /** Set of all valid image paths */
142
+ validImages: Set<string>;
143
+ }
144
+ /**
145
+ * A lint rule definition
146
+ */
147
+ interface Rule {
148
+ /** Unique rule identifier */
149
+ name: string;
150
+ /** Whether violations block the build */
151
+ severity: Severity;
152
+ /** Rule implementation */
153
+ run: (item: ContentItem, context: RuleContext) => LintResult[];
154
+ /** Machine-readable fix strategy for AI coding agents */
155
+ fixStrategy?: string;
156
+ /** Rule category for grouping (seo, geo, content, technical, i18n) */
157
+ category?: string;
158
+ }
159
+ /**
160
+ * A global rule that validates project-wide concerns (not per-item)
161
+ */
162
+ interface GlobalRule {
163
+ /** Unique rule identifier */
164
+ name: string;
165
+ /** Whether violations block the build */
166
+ severity: Severity;
167
+ /** Rule implementation (no item — validates global state) */
168
+ run: () => LintResult[];
169
+ }
170
+ /**
171
+ * Extracted heading from MDX content
172
+ */
173
+ interface Heading {
174
+ level: number;
175
+ text: string;
176
+ line: number;
177
+ }
178
+ /**
179
+ * Extracted link from MDX content
180
+ */
181
+ interface ExtractedLink {
182
+ url: string;
183
+ text: string;
184
+ line: number;
185
+ isInternal: boolean;
186
+ originalUrl: string;
187
+ }
188
+ /**
189
+ * Extracted image from MDX content
190
+ */
191
+ interface ExtractedImage {
192
+ src: string;
193
+ alt: string;
194
+ line: number;
195
+ hasAlt: boolean;
196
+ source: 'frontmatter' | 'inline';
197
+ }
198
+
199
+ /**
200
+ * @ijonis/geo-lint Content Adapter Types
201
+ */
202
+
203
+ /**
204
+ * Adapter interface for custom content sources.
205
+ * Implement this to integrate geo-lint with any CMS or content pipeline.
206
+ */
207
+ interface ContentAdapter {
208
+ /** Load all content items from the source */
209
+ loadItems(projectRoot: string): ContentItem[] | Promise<ContentItem[]>;
210
+ }
211
+ /**
212
+ * Create an adapter from a simple function
213
+ */
214
+ declare function createAdapter(fn: (projectRoot: string) => ContentItem[] | Promise<ContentItem[]>): ContentAdapter;
215
+
216
+ /**
217
+ * @ijonis/geo-lint Configuration Loader
218
+ */
219
+
220
+ /**
221
+ * Helper for TypeScript-aware config files. Provides autocomplete in user configs.
222
+ */
223
+ declare function defineConfig(config: GeoLintUserConfig): GeoLintUserConfig;
224
+
225
+ /**
226
+ * @ijonis/geo-lint MDX Content Adapter
227
+ * Scans directories for MDX/Markdown files and parses them with gray-matter
228
+ */
229
+
230
+ /**
231
+ * Load all content items from configured content paths.
232
+ * Uses gray-matter for robust YAML frontmatter parsing.
233
+ */
234
+ declare function loadContentItems(contentPaths: ContentPathConfig[], projectRoot: string): ContentItem[];
235
+
236
+ /**
237
+ * @ijonis/geo-lint
238
+ * SEO and GEO (Generative Engine Optimization) linter for Markdown/MDX content
239
+ *
240
+ * The first open-source linter that checks your content for AI search visibility.
241
+ */
242
+
243
+ /** Options for the lint() function */
244
+ interface LintOptions {
245
+ /** Project root directory (defaults to cwd) */
246
+ projectRoot?: string;
247
+ /** Explicit config (skips file loading) */
248
+ config?: GeoLintUserConfig;
249
+ /** Custom content adapter (skips default MDX scanning) */
250
+ adapter?: ContentAdapter;
251
+ /** Global rules to run after per-item rules */
252
+ globalRules?: GlobalRule[];
253
+ /** Output format: 'pretty' for terminal, 'json' for machine consumption */
254
+ format?: 'pretty' | 'json';
255
+ }
256
+ /**
257
+ * Run the complete GEO lint process.
258
+ * Returns exit code: 0 for success (no errors), 1 for errors found.
259
+ */
260
+ declare function lint(options?: LintOptions): Promise<number>;
261
+ /**
262
+ * Run the linter and return raw results (no console output).
263
+ * Useful for programmatic integration.
264
+ */
265
+ declare function lintQuiet(options?: LintOptions): Promise<LintResult[]>;
266
+
267
+ export { type ContentAdapter, type ContentItem, type ContentPathConfig, type ContentType, type ExtractedImage, type ExtractedLink, type GeoConfig, type GeoLintConfig, type GeoLintUserConfig, type GlobalRule, type Heading, type LintOptions, type LintResult, type Rule, type RuleContext, type Severity, type ThresholdConfig, createAdapter, defineConfig, lint, lintQuiet, loadContentItems };
@@ -0,0 +1,267 @@
1
+ /**
2
+ * @ijonis/geo-lint Configuration Types
3
+ */
4
+ /** Content directory configuration */
5
+ interface ContentPathConfig {
6
+ /** Relative path from project root, e.g. 'content/blog' */
7
+ dir: string;
8
+ /** Content type identifier */
9
+ type: 'blog' | 'page' | 'project';
10
+ /** URL prefix for permalink derivation, e.g. '/blog/' */
11
+ urlPrefix?: string;
12
+ /** Default locale when frontmatter has no locale field */
13
+ defaultLocale?: string;
14
+ }
15
+ /** GEO-specific configuration */
16
+ interface GeoConfig {
17
+ /** Brand name for entity density rule, e.g. 'ACME Corp'. Empty = skip check */
18
+ brandName: string;
19
+ /** Primary city/location for entity density rule, e.g. 'Berlin'. Empty = skip check */
20
+ brandCity: string;
21
+ /** Path to geo-keywords markdown file, relative to project root. Empty = skip */
22
+ keywordsPath: string;
23
+ }
24
+ /** User-facing configuration (partial, with defaults applied) */
25
+ interface GeoLintUserConfig {
26
+ /** Canonical site URL, e.g. 'https://example.com' (required) */
27
+ siteUrl: string;
28
+ /** Content directory configurations */
29
+ contentPaths?: ContentPathConfig[];
30
+ /** Static routes valid for link validation (e.g. ['/about', '/contact']) */
31
+ staticRoutes?: string[];
32
+ /** Image directories to scan, relative to project root */
33
+ imageDirectories?: string[];
34
+ /** Valid blog categories (empty = skip category-invalid rule) */
35
+ categories?: string[];
36
+ /** Slugs to fully exclude from linting */
37
+ excludeSlugs?: string[];
38
+ /** Content categories to fully exclude (e.g. 'legal') */
39
+ excludeCategories?: string[];
40
+ /** GEO-specific configuration */
41
+ geo?: Partial<GeoConfig>;
42
+ /** Rule severity overrides: 'off' disables a rule */
43
+ rules?: Record<string, 'error' | 'warning' | 'off'>;
44
+ /** Threshold overrides */
45
+ thresholds?: Partial<ThresholdConfig>;
46
+ }
47
+ /** Threshold configuration for validation rules */
48
+ interface ThresholdConfig {
49
+ title: {
50
+ minLength: number;
51
+ maxLength: number;
52
+ warnLength: number;
53
+ };
54
+ description: {
55
+ minLength: number;
56
+ maxLength: number;
57
+ warnLength: number;
58
+ };
59
+ slug: {
60
+ maxLength: number;
61
+ };
62
+ content: {
63
+ minWordCount: number;
64
+ minReadabilityScore: number;
65
+ };
66
+ }
67
+ /** Fully resolved configuration with all defaults applied */
68
+ interface GeoLintConfig {
69
+ siteUrl: string;
70
+ contentPaths: ContentPathConfig[];
71
+ staticRoutes: string[];
72
+ imageDirectories: string[];
73
+ categories: string[];
74
+ excludeSlugs: string[];
75
+ excludeCategories: string[];
76
+ geo: GeoConfig;
77
+ rules: Record<string, 'error' | 'warning' | 'off'>;
78
+ thresholds: ThresholdConfig;
79
+ }
80
+
81
+ /**
82
+ * @ijonis/geo-lint Core Types
83
+ * Shared interfaces for the SEO/GEO validation system
84
+ */
85
+ type Severity = 'error' | 'warning';
86
+ type ContentType = 'blog' | 'page' | 'project';
87
+ /**
88
+ * Result of a single lint check
89
+ */
90
+ interface LintResult {
91
+ /** Relative path: blog/my-post-slug */
92
+ file: string;
93
+ /** Field or area checked (e.g., 'title', 'body', 'links') */
94
+ field: string;
95
+ /** Rule identifier (e.g., 'title-too-long', 'geo-no-question-headings') */
96
+ rule: string;
97
+ /** Whether this blocks the build */
98
+ severity: Severity;
99
+ /** Human-readable error message */
100
+ message: string;
101
+ /** Optional actionable fix suggestion */
102
+ suggestion?: string;
103
+ /** Line number in MDX file if applicable */
104
+ line?: number;
105
+ }
106
+ /**
107
+ * Parsed content item combining frontmatter metadata and raw MDX body
108
+ */
109
+ interface ContentItem {
110
+ title: string;
111
+ slug: string;
112
+ description: string;
113
+ permalink: string;
114
+ image?: string;
115
+ imageAlt?: string;
116
+ categories?: string[];
117
+ date?: string;
118
+ category?: string;
119
+ locale?: string;
120
+ translationKey?: string;
121
+ updatedAt?: string;
122
+ noindex?: boolean;
123
+ draft?: boolean;
124
+ /** Content type: blog, page, or project */
125
+ contentType: ContentType;
126
+ /** Full path to source file */
127
+ filePath: string;
128
+ /** Raw file content (frontmatter + body) */
129
+ rawContent: string;
130
+ /** Body content without frontmatter */
131
+ body: string;
132
+ }
133
+ /**
134
+ * Context passed to rules for cross-content validation
135
+ */
136
+ interface RuleContext {
137
+ /** All content items (including excluded) for link/duplicate detection */
138
+ allContent: ContentItem[];
139
+ /** Set of all valid internal URL paths */
140
+ validSlugs: Set<string>;
141
+ /** Set of all valid image paths */
142
+ validImages: Set<string>;
143
+ }
144
+ /**
145
+ * A lint rule definition
146
+ */
147
+ interface Rule {
148
+ /** Unique rule identifier */
149
+ name: string;
150
+ /** Whether violations block the build */
151
+ severity: Severity;
152
+ /** Rule implementation */
153
+ run: (item: ContentItem, context: RuleContext) => LintResult[];
154
+ /** Machine-readable fix strategy for AI coding agents */
155
+ fixStrategy?: string;
156
+ /** Rule category for grouping (seo, geo, content, technical, i18n) */
157
+ category?: string;
158
+ }
159
+ /**
160
+ * A global rule that validates project-wide concerns (not per-item)
161
+ */
162
+ interface GlobalRule {
163
+ /** Unique rule identifier */
164
+ name: string;
165
+ /** Whether violations block the build */
166
+ severity: Severity;
167
+ /** Rule implementation (no item — validates global state) */
168
+ run: () => LintResult[];
169
+ }
170
+ /**
171
+ * Extracted heading from MDX content
172
+ */
173
+ interface Heading {
174
+ level: number;
175
+ text: string;
176
+ line: number;
177
+ }
178
+ /**
179
+ * Extracted link from MDX content
180
+ */
181
+ interface ExtractedLink {
182
+ url: string;
183
+ text: string;
184
+ line: number;
185
+ isInternal: boolean;
186
+ originalUrl: string;
187
+ }
188
+ /**
189
+ * Extracted image from MDX content
190
+ */
191
+ interface ExtractedImage {
192
+ src: string;
193
+ alt: string;
194
+ line: number;
195
+ hasAlt: boolean;
196
+ source: 'frontmatter' | 'inline';
197
+ }
198
+
199
+ /**
200
+ * @ijonis/geo-lint Content Adapter Types
201
+ */
202
+
203
+ /**
204
+ * Adapter interface for custom content sources.
205
+ * Implement this to integrate geo-lint with any CMS or content pipeline.
206
+ */
207
+ interface ContentAdapter {
208
+ /** Load all content items from the source */
209
+ loadItems(projectRoot: string): ContentItem[] | Promise<ContentItem[]>;
210
+ }
211
+ /**
212
+ * Create an adapter from a simple function
213
+ */
214
+ declare function createAdapter(fn: (projectRoot: string) => ContentItem[] | Promise<ContentItem[]>): ContentAdapter;
215
+
216
+ /**
217
+ * @ijonis/geo-lint Configuration Loader
218
+ */
219
+
220
+ /**
221
+ * Helper for TypeScript-aware config files. Provides autocomplete in user configs.
222
+ */
223
+ declare function defineConfig(config: GeoLintUserConfig): GeoLintUserConfig;
224
+
225
+ /**
226
+ * @ijonis/geo-lint MDX Content Adapter
227
+ * Scans directories for MDX/Markdown files and parses them with gray-matter
228
+ */
229
+
230
+ /**
231
+ * Load all content items from configured content paths.
232
+ * Uses gray-matter for robust YAML frontmatter parsing.
233
+ */
234
+ declare function loadContentItems(contentPaths: ContentPathConfig[], projectRoot: string): ContentItem[];
235
+
236
+ /**
237
+ * @ijonis/geo-lint
238
+ * SEO and GEO (Generative Engine Optimization) linter for Markdown/MDX content
239
+ *
240
+ * The first open-source linter that checks your content for AI search visibility.
241
+ */
242
+
243
+ /** Options for the lint() function */
244
+ interface LintOptions {
245
+ /** Project root directory (defaults to cwd) */
246
+ projectRoot?: string;
247
+ /** Explicit config (skips file loading) */
248
+ config?: GeoLintUserConfig;
249
+ /** Custom content adapter (skips default MDX scanning) */
250
+ adapter?: ContentAdapter;
251
+ /** Global rules to run after per-item rules */
252
+ globalRules?: GlobalRule[];
253
+ /** Output format: 'pretty' for terminal, 'json' for machine consumption */
254
+ format?: 'pretty' | 'json';
255
+ }
256
+ /**
257
+ * Run the complete GEO lint process.
258
+ * Returns exit code: 0 for success (no errors), 1 for errors found.
259
+ */
260
+ declare function lint(options?: LintOptions): Promise<number>;
261
+ /**
262
+ * Run the linter and return raw results (no console output).
263
+ * Useful for programmatic integration.
264
+ */
265
+ declare function lintQuiet(options?: LintOptions): Promise<LintResult[]>;
266
+
267
+ export { type ContentAdapter, type ContentItem, type ContentPathConfig, type ContentType, type ExtractedImage, type ExtractedLink, type GeoConfig, type GeoLintConfig, type GeoLintUserConfig, type GlobalRule, type Heading, type LintOptions, type LintResult, type Rule, type RuleContext, type Severity, type ThresholdConfig, createAdapter, defineConfig, lint, lintQuiet, loadContentItems };