@rankcli/agent-runtime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/analyzer-2CSWIQGD.mjs +6 -0
- package/dist/chunk-YNZYHEYM.mjs +774 -0
- package/dist/index.d.mts +4012 -0
- package/dist/index.d.ts +4012 -0
- package/dist/index.js +29672 -0
- package/dist/index.mjs +28602 -0
- package/package.json +53 -0
- package/scripts/build-deno.ts +134 -0
- package/src/audit/ai/analyzer.ts +347 -0
- package/src/audit/ai/index.ts +29 -0
- package/src/audit/ai/prompts/content-analysis.ts +271 -0
- package/src/audit/ai/types.ts +179 -0
- package/src/audit/checks/additional-checks.ts +439 -0
- package/src/audit/checks/ai-citation-worthiness.ts +399 -0
- package/src/audit/checks/ai-content-structure.ts +325 -0
- package/src/audit/checks/ai-readiness.ts +339 -0
- package/src/audit/checks/anchor-text.ts +179 -0
- package/src/audit/checks/answer-conciseness.ts +322 -0
- package/src/audit/checks/asset-minification.ts +270 -0
- package/src/audit/checks/bing-optimization.ts +206 -0
- package/src/audit/checks/brand-mention-optimization.ts +349 -0
- package/src/audit/checks/caching-headers.ts +305 -0
- package/src/audit/checks/canonical-advanced.ts +150 -0
- package/src/audit/checks/canonical-domain.ts +196 -0
- package/src/audit/checks/citation-quality.ts +358 -0
- package/src/audit/checks/client-rendering.ts +542 -0
- package/src/audit/checks/color-contrast.ts +342 -0
- package/src/audit/checks/content-freshness.ts +170 -0
- package/src/audit/checks/content-science.ts +589 -0
- package/src/audit/checks/conversion-elements.ts +526 -0
- package/src/audit/checks/crawlability.ts +220 -0
- package/src/audit/checks/directory-listing.ts +172 -0
- package/src/audit/checks/dom-analysis.ts +191 -0
- package/src/audit/checks/dom-size.ts +246 -0
- package/src/audit/checks/duplicate-content.ts +194 -0
- package/src/audit/checks/eeat-signals.ts +990 -0
- package/src/audit/checks/entity-seo.ts +396 -0
- package/src/audit/checks/featured-snippet.ts +473 -0
- package/src/audit/checks/freshness-signals.ts +443 -0
- package/src/audit/checks/funnel-intent.ts +463 -0
- package/src/audit/checks/hreflang.ts +174 -0
- package/src/audit/checks/html-compliance.ts +302 -0
- package/src/audit/checks/image-dimensions.ts +167 -0
- package/src/audit/checks/images.ts +160 -0
- package/src/audit/checks/indexnow.ts +275 -0
- package/src/audit/checks/interactive-tools.ts +475 -0
- package/src/audit/checks/internal-link-graph.ts +436 -0
- package/src/audit/checks/keyword-analysis.ts +239 -0
- package/src/audit/checks/keyword-cannibalization.ts +385 -0
- package/src/audit/checks/keyword-placement.ts +471 -0
- package/src/audit/checks/links.ts +203 -0
- package/src/audit/checks/llms-txt.ts +224 -0
- package/src/audit/checks/local-seo.ts +296 -0
- package/src/audit/checks/mobile.ts +167 -0
- package/src/audit/checks/modern-images.ts +226 -0
- package/src/audit/checks/navboost-signals.ts +395 -0
- package/src/audit/checks/on-page.ts +209 -0
- package/src/audit/checks/page-resources.ts +285 -0
- package/src/audit/checks/pagination.ts +180 -0
- package/src/audit/checks/performance.ts +153 -0
- package/src/audit/checks/platform-presence.ts +580 -0
- package/src/audit/checks/redirect-analysis.ts +153 -0
- package/src/audit/checks/redirect-chain.ts +389 -0
- package/src/audit/checks/resource-hints.ts +420 -0
- package/src/audit/checks/responsive-css.ts +247 -0
- package/src/audit/checks/responsive-images.ts +396 -0
- package/src/audit/checks/review-ecosystem.ts +415 -0
- package/src/audit/checks/robots-validation.ts +373 -0
- package/src/audit/checks/security-headers.ts +172 -0
- package/src/audit/checks/security.ts +144 -0
- package/src/audit/checks/serp-preview.ts +251 -0
- package/src/audit/checks/site-maturity.ts +444 -0
- package/src/audit/checks/social-meta.test.ts +275 -0
- package/src/audit/checks/social-meta.ts +134 -0
- package/src/audit/checks/soft-404.ts +151 -0
- package/src/audit/checks/structured-data.ts +238 -0
- package/src/audit/checks/tech-detection.ts +496 -0
- package/src/audit/checks/topical-clusters.ts +435 -0
- package/src/audit/checks/tracker-bloat.ts +462 -0
- package/src/audit/checks/tracking-verification.test.ts +371 -0
- package/src/audit/checks/tracking-verification.ts +636 -0
- package/src/audit/checks/url-safety.ts +682 -0
- package/src/audit/deno-entry.ts +66 -0
- package/src/audit/discovery/index.ts +15 -0
- package/src/audit/discovery/link-crawler.ts +232 -0
- package/src/audit/discovery/repo-routes.ts +347 -0
- package/src/audit/engine.ts +620 -0
- package/src/audit/fixes/index.ts +209 -0
- package/src/audit/fixes/social-meta-fixes.test.ts +329 -0
- package/src/audit/fixes/social-meta-fixes.ts +463 -0
- package/src/audit/index.ts +74 -0
- package/src/audit/runner.test.ts +299 -0
- package/src/audit/runner.ts +130 -0
- package/src/audit/types.ts +1953 -0
- package/src/content/featured-snippet.ts +367 -0
- package/src/content/generator.test.ts +534 -0
- package/src/content/generator.ts +501 -0
- package/src/content/headline.ts +317 -0
- package/src/content/index.ts +62 -0
- package/src/content/intent.ts +258 -0
- package/src/content/keyword-density.ts +349 -0
- package/src/content/readability.ts +262 -0
- package/src/executor.ts +336 -0
- package/src/fixer.ts +416 -0
- package/src/frameworks/detector.test.ts +248 -0
- package/src/frameworks/detector.ts +371 -0
- package/src/frameworks/index.ts +68 -0
- package/src/frameworks/recipes/angular.yaml +171 -0
- package/src/frameworks/recipes/astro.yaml +206 -0
- package/src/frameworks/recipes/django.yaml +180 -0
- package/src/frameworks/recipes/laravel.yaml +137 -0
- package/src/frameworks/recipes/nextjs.yaml +268 -0
- package/src/frameworks/recipes/nuxt.yaml +175 -0
- package/src/frameworks/recipes/rails.yaml +188 -0
- package/src/frameworks/recipes/react.yaml +202 -0
- package/src/frameworks/recipes/sveltekit.yaml +154 -0
- package/src/frameworks/recipes/vue.yaml +137 -0
- package/src/frameworks/recipes/wordpress.yaml +209 -0
- package/src/frameworks/suggestion-engine.ts +320 -0
- package/src/geo/geo-content.test.ts +305 -0
- package/src/geo/geo-content.ts +266 -0
- package/src/geo/geo-history.test.ts +473 -0
- package/src/geo/geo-history.ts +433 -0
- package/src/geo/geo-tracker.test.ts +359 -0
- package/src/geo/geo-tracker.ts +411 -0
- package/src/geo/index.ts +10 -0
- package/src/git/commit-helper.test.ts +261 -0
- package/src/git/commit-helper.ts +329 -0
- package/src/git/index.ts +12 -0
- package/src/git/pr-helper.test.ts +284 -0
- package/src/git/pr-helper.ts +307 -0
- package/src/index.ts +66 -0
- package/src/keywords/ai-keyword-engine.ts +1062 -0
- package/src/keywords/ai-summarizer.ts +387 -0
- package/src/keywords/ci-mode.ts +555 -0
- package/src/keywords/engine.ts +359 -0
- package/src/keywords/index.ts +151 -0
- package/src/keywords/llm-judge.ts +357 -0
- package/src/keywords/nlp-analysis.ts +706 -0
- package/src/keywords/prioritizer.ts +295 -0
- package/src/keywords/site-crawler.ts +342 -0
- package/src/keywords/sources/autocomplete.ts +139 -0
- package/src/keywords/sources/competitive-search.ts +450 -0
- package/src/keywords/sources/competitor-analysis.ts +374 -0
- package/src/keywords/sources/dataforseo.ts +206 -0
- package/src/keywords/sources/free-sources.ts +294 -0
- package/src/keywords/sources/gsc.ts +123 -0
- package/src/keywords/topic-grouping.ts +327 -0
- package/src/keywords/types.ts +144 -0
- package/src/keywords/wizard.ts +457 -0
- package/src/loader.ts +40 -0
- package/src/reports/index.ts +7 -0
- package/src/reports/report-generator.test.ts +293 -0
- package/src/reports/report-generator.ts +713 -0
- package/src/scheduler/alerts.test.ts +458 -0
- package/src/scheduler/alerts.ts +328 -0
- package/src/scheduler/index.ts +8 -0
- package/src/scheduler/scheduled-audit.test.ts +377 -0
- package/src/scheduler/scheduled-audit.ts +149 -0
- package/src/test/integration-test.ts +325 -0
- package/src/tools/analyzer.ts +373 -0
- package/src/tools/crawl.ts +293 -0
- package/src/tools/files.ts +301 -0
- package/src/tools/h1-fixer.ts +249 -0
- package/src/tools/index.ts +67 -0
- package/src/tracking/github-action.ts +326 -0
- package/src/tracking/google-analytics.ts +265 -0
- package/src/tracking/index.ts +45 -0
- package/src/tracking/report-generator.ts +386 -0
- package/src/tracking/search-console.ts +335 -0
- package/src/types.ts +134 -0
- package/src/utils/http.ts +302 -0
- package/src/wasm-adapter.ts +297 -0
- package/src/wasm-entry.ts +14 -0
- package/tsconfig.json +17 -0
- package/tsup.wasm.config.ts +26 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
generateHTMLReport,
|
|
4
|
+
generatePDFReport,
|
|
5
|
+
ReportConfig,
|
|
6
|
+
ReportBranding,
|
|
7
|
+
AuditReportData,
|
|
8
|
+
} from './report-generator.js';
|
|
9
|
+
|
|
10
|
+
describe('report-generator', () => {
|
|
11
|
+
const mockAuditData: AuditReportData = {
|
|
12
|
+
url: 'https://example.com',
|
|
13
|
+
domain: 'example.com',
|
|
14
|
+
timestamp: '2024-01-15T10:00:00Z',
|
|
15
|
+
score: 72,
|
|
16
|
+
healthScores: {
|
|
17
|
+
overall: 72,
|
|
18
|
+
crawlability: 80,
|
|
19
|
+
indexability: 75,
|
|
20
|
+
onPage: 65,
|
|
21
|
+
content: 70,
|
|
22
|
+
links: 75,
|
|
23
|
+
performance: 70,
|
|
24
|
+
security: 85,
|
|
25
|
+
aiReadiness: 70,
|
|
26
|
+
social: 65,
|
|
27
|
+
localSeo: 60,
|
|
28
|
+
},
|
|
29
|
+
summary: {
|
|
30
|
+
errors: 3,
|
|
31
|
+
warnings: 8,
|
|
32
|
+
notices: 5,
|
|
33
|
+
passed: 84,
|
|
34
|
+
},
|
|
35
|
+
issues: [
|
|
36
|
+
{ code: 'TITLE_MISSING', severity: 'error', category: 'on-page', title: 'Missing title', affectedUrls: ['https://example.com/about'] },
|
|
37
|
+
{ code: 'META_DESC_MISSING', severity: 'error', category: 'on-page', title: 'Missing meta description', affectedUrls: ['https://example.com/about'] },
|
|
38
|
+
{ code: 'OG_IMAGE_MISSING', severity: 'warning', category: 'social', title: 'Missing OG image', affectedUrls: ['https://example.com'] },
|
|
39
|
+
],
|
|
40
|
+
pages: [
|
|
41
|
+
{ url: 'https://example.com', score: 75, issues: 2 },
|
|
42
|
+
{ url: 'https://example.com/about', score: 60, issues: 4 },
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('generateHTMLReport', () => {
|
|
47
|
+
it('generates valid HTML document', () => {
|
|
48
|
+
const html = generateHTMLReport(mockAuditData);
|
|
49
|
+
|
|
50
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
51
|
+
expect(html).toContain('<html');
|
|
52
|
+
expect(html).toContain('</html>');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('includes audit score prominently', () => {
|
|
56
|
+
const html = generateHTMLReport(mockAuditData);
|
|
57
|
+
|
|
58
|
+
expect(html).toContain('72');
|
|
59
|
+
expect(html).toContain('/100');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('includes domain and timestamp', () => {
|
|
63
|
+
const html = generateHTMLReport(mockAuditData);
|
|
64
|
+
|
|
65
|
+
expect(html).toContain('example.com');
|
|
66
|
+
expect(html).toContain('2024');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('includes summary statistics', () => {
|
|
70
|
+
const html = generateHTMLReport(mockAuditData);
|
|
71
|
+
|
|
72
|
+
expect(html).toContain('3'); // errors
|
|
73
|
+
expect(html).toContain('8'); // warnings
|
|
74
|
+
expect(html).toContain('84'); // passed
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('includes category scores', () => {
|
|
78
|
+
const html = generateHTMLReport(mockAuditData);
|
|
79
|
+
|
|
80
|
+
expect(html).toContain('Crawlability');
|
|
81
|
+
expect(html).toContain('80');
|
|
82
|
+
expect(html).toContain('On-Page');
|
|
83
|
+
expect(html).toContain('65');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('lists issues with severity badges', () => {
|
|
87
|
+
const html = generateHTMLReport(mockAuditData);
|
|
88
|
+
|
|
89
|
+
expect(html).toContain('TITLE_MISSING');
|
|
90
|
+
expect(html).toContain('Missing title');
|
|
91
|
+
expect(html).toContain('error');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('includes page-level details', () => {
|
|
95
|
+
const html = generateHTMLReport(mockAuditData);
|
|
96
|
+
|
|
97
|
+
expect(html).toContain('example.com/about');
|
|
98
|
+
expect(html).toContain('60');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('applies default branding when none provided', () => {
|
|
102
|
+
const html = generateHTMLReport(mockAuditData);
|
|
103
|
+
|
|
104
|
+
expect(html).toContain('SEO Autopilot');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('applies custom branding when provided', () => {
|
|
108
|
+
const branding: ReportBranding = {
|
|
109
|
+
companyName: 'ACME Agency',
|
|
110
|
+
logoUrl: 'https://acme.com/logo.png',
|
|
111
|
+
primaryColor: '#FF5733',
|
|
112
|
+
accentColor: '#33FF57',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const html = generateHTMLReport(mockAuditData, { branding });
|
|
116
|
+
|
|
117
|
+
expect(html).toContain('ACME Agency');
|
|
118
|
+
expect(html).toContain('https://acme.com/logo.png');
|
|
119
|
+
expect(html).toContain('#FF5733');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('removes SEO Autopilot branding in white-label mode', () => {
|
|
123
|
+
const branding: ReportBranding = {
|
|
124
|
+
companyName: 'ACME Agency',
|
|
125
|
+
whiteLabel: true,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const html = generateHTMLReport(mockAuditData, { branding });
|
|
129
|
+
|
|
130
|
+
expect(html).not.toContain('SEO Autopilot');
|
|
131
|
+
expect(html).toContain('ACME Agency');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('includes CSS styling inline', () => {
|
|
135
|
+
const html = generateHTMLReport(mockAuditData);
|
|
136
|
+
|
|
137
|
+
expect(html).toContain('<style>');
|
|
138
|
+
expect(html).toContain('</style>');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('generates responsive layout', () => {
|
|
142
|
+
const html = generateHTMLReport(mockAuditData);
|
|
143
|
+
|
|
144
|
+
expect(html).toContain('viewport');
|
|
145
|
+
expect(html).toContain('@media');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('generatePDFReport', () => {
|
|
150
|
+
it('returns a Buffer', async () => {
|
|
151
|
+
const pdf = await generatePDFReport(mockAuditData);
|
|
152
|
+
|
|
153
|
+
expect(pdf).toBeInstanceOf(Buffer);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('generates non-empty PDF', async () => {
|
|
157
|
+
const pdf = await generatePDFReport(mockAuditData);
|
|
158
|
+
|
|
159
|
+
expect(pdf.length).toBeGreaterThan(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('applies custom branding', async () => {
|
|
163
|
+
const branding: ReportBranding = {
|
|
164
|
+
companyName: 'ACME Agency',
|
|
165
|
+
whiteLabel: true,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// PDF generation should not throw with custom branding
|
|
169
|
+
const pdf = await generatePDFReport(mockAuditData, { branding });
|
|
170
|
+
expect(pdf).toBeInstanceOf(Buffer);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('ReportConfig options', () => {
|
|
175
|
+
it('includes executive summary when enabled', () => {
|
|
176
|
+
const config: ReportConfig = {
|
|
177
|
+
includeExecutiveSummary: true,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const html = generateHTMLReport(mockAuditData, config);
|
|
181
|
+
|
|
182
|
+
expect(html).toContain('Executive Summary');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('excludes page details when disabled', () => {
|
|
186
|
+
const config: ReportConfig = {
|
|
187
|
+
includePageDetails: false,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const html = generateHTMLReport(mockAuditData, config);
|
|
191
|
+
|
|
192
|
+
expect(html).not.toContain('Page Details');
|
|
193
|
+
// The issues section may still contain URLs, but the page details table should be excluded
|
|
194
|
+
expect(html).not.toContain('<table class="pages-table">');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('includes recommendations when enabled', () => {
|
|
198
|
+
const config: ReportConfig = {
|
|
199
|
+
includeRecommendations: true,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const html = generateHTMLReport(mockAuditData, config);
|
|
203
|
+
|
|
204
|
+
expect(html).toContain('Recommendations');
|
|
205
|
+
expect(html).toContain('How to Fix');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('limits issues shown when maxIssues set', () => {
|
|
209
|
+
const dataWithManyIssues: AuditReportData = {
|
|
210
|
+
...mockAuditData,
|
|
211
|
+
issues: Array.from({ length: 20 }, (_, i) => ({
|
|
212
|
+
code: `ISSUE_${i}`,
|
|
213
|
+
severity: 'warning' as const,
|
|
214
|
+
category: 'on-page',
|
|
215
|
+
title: `Issue ${i}`,
|
|
216
|
+
affectedUrls: [],
|
|
217
|
+
})),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const config: ReportConfig = {
|
|
221
|
+
maxIssues: 5,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const html = generateHTMLReport(dataWithManyIssues, config);
|
|
225
|
+
|
|
226
|
+
// Should show "and X more" indicator
|
|
227
|
+
expect(html).toContain('more');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('score visualization', () => {
|
|
232
|
+
it('shows green score badge for high scores', () => {
|
|
233
|
+
const highScoreData = { ...mockAuditData, score: 90 };
|
|
234
|
+
const html = generateHTMLReport(highScoreData);
|
|
235
|
+
|
|
236
|
+
expect(html).toMatch(/green|success|good/i);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('shows yellow score badge for medium scores', () => {
|
|
240
|
+
const mediumScoreData = { ...mockAuditData, score: 60 };
|
|
241
|
+
const html = generateHTMLReport(mediumScoreData);
|
|
242
|
+
|
|
243
|
+
expect(html).toMatch(/yellow|warning|orange/i);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('shows red score badge for low scores', () => {
|
|
247
|
+
const lowScoreData = { ...mockAuditData, score: 30 };
|
|
248
|
+
const html = generateHTMLReport(lowScoreData);
|
|
249
|
+
|
|
250
|
+
expect(html).toMatch(/red|error|danger/i);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('integration scenarios', () => {
|
|
255
|
+
it('generates complete agency report', () => {
|
|
256
|
+
const config: ReportConfig = {
|
|
257
|
+
branding: {
|
|
258
|
+
companyName: 'Digital Marketing Pro',
|
|
259
|
+
logoUrl: 'https://dmp.com/logo.svg',
|
|
260
|
+
primaryColor: '#1a73e8',
|
|
261
|
+
accentColor: '#34a853',
|
|
262
|
+
whiteLabel: true,
|
|
263
|
+
contactEmail: 'reports@dmp.com',
|
|
264
|
+
websiteUrl: 'https://dmp.com',
|
|
265
|
+
},
|
|
266
|
+
includeExecutiveSummary: true,
|
|
267
|
+
includeRecommendations: true,
|
|
268
|
+
includePageDetails: true,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const html = generateHTMLReport(mockAuditData, config);
|
|
272
|
+
|
|
273
|
+
expect(html).toContain('Digital Marketing Pro');
|
|
274
|
+
expect(html).toContain('Executive Summary');
|
|
275
|
+
expect(html).toContain('Recommendations');
|
|
276
|
+
expect(html).not.toContain('SEO Autopilot');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('generates minimal free report', () => {
|
|
280
|
+
const config: ReportConfig = {
|
|
281
|
+
includeExecutiveSummary: false,
|
|
282
|
+
includeRecommendations: false,
|
|
283
|
+
includePageDetails: false,
|
|
284
|
+
maxIssues: 5,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const html = generateHTMLReport(mockAuditData, config);
|
|
288
|
+
|
|
289
|
+
expect(html).toContain('SEO Autopilot');
|
|
290
|
+
expect(html).not.toContain('Executive Summary');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|