@pas7/llm-seo 0.1.6 → 0.3.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
@@ -12,6 +12,7 @@ Deterministic LLM SEO artifacts generator & validator for modern static sites (N
12
12
  One config in, deterministic artifacts out:
13
13
  - generate `llms.txt` and `llms-full.txt`
14
14
  - build canonical URLs from manifest items
15
+ - support mixed routing per manifest section (`prefix`, `suffix`, `locale-segment`, `custom`)
15
16
  - lint policy constraints (restricted claims, duplicates, empty sections)
16
17
  - check generated files in CI with explicit exit codes
17
18
 
@@ -42,10 +43,18 @@ export default {
42
43
  hubs: ['/services', '/blog', '/projects', '/cases', '/contact'],
43
44
  },
44
45
  manifests: {
45
- blog: [
46
- { slug: '/blog/llm-seo-basics', locales: ['en', 'uk'] },
47
- { slug: '/blog/canonical-strategy', locales: ['en'] },
48
- ],
46
+ blog: {
47
+ sectionPath: '/blog',
48
+ routeStyle: 'locale-segment',
49
+ items: [
50
+ { slug: '/llm-seo-basics', locales: ['en', 'uk'] },
51
+ { slug: '/canonical-strategy', locales: ['en'] },
52
+ ],
53
+ },
54
+ contactPages: {
55
+ routeStyle: 'suffix',
56
+ items: [{ slug: '/contact', locales: ['en', 'uk'] }],
57
+ },
49
58
  },
50
59
  contact: {
51
60
  email: 'contact@example.com',
@@ -101,6 +110,7 @@ llm-seo check [options]
101
110
  Options:
102
111
  -c, --config <path> Path to config file
103
112
  --fail-on <level> fail threshold: warn|error (default: error)
113
+ --check-machine-hints-live Live-check machine hint URLs over HTTP
104
114
  -v, --verbose Verbose logs
105
115
  ```
106
116
 
@@ -146,6 +156,18 @@ import {
146
156
 
147
157
  Use helpers from `@pas7/llm-seo/adapters/next` to normalize manifest items and build scripts.
148
158
  See [`examples/next-static-export`](./examples/next-static-export).
159
+ For hybrid section routing, use `createSectionManifest(...)` and `applySectionCanonicalOverrides(...)`.
160
+ Works with `@pas7/nextjs-sitemap-hreflang` in one build pipeline:
161
+
162
+ ```bash
163
+ llm-seo generate --config llm-seo.config.ts
164
+ next build
165
+ nextjs-sitemap-hreflang check --in out/sitemap.xml --fail-on-missing
166
+ llm-seo check --config llm-seo.config.ts --fail-on error
167
+ ```
168
+
169
+ Hybrid routing example:
170
+ - [`examples/next-static-export-hybrid-routing`](./examples/next-static-export-hybrid-routing)
149
171
 
150
172
  ## Contributing and Security
151
173
 
@@ -1,3 +1,4 @@
1
- export { BuildHookOptions, BuildHookResult, BuildScriptsResult, CreateManifestFromDataOptions, CreateManifestFromPagesDirOptions, CreateNextConfigOptions, CreateRobotsLlmsPolicySnippetOptions, FromNextContentManifestOptions, GenerateBuildScriptsOptions, NextConfigResult, NextContentManifest, NextManifestOptions, createManifestFromData, createManifestFromPagesDir, createNextConfig, createNextPlugin, createRobotsLlmsPolicySnippet, extractPagePaths, fromNextContentManifest, generateBuildScripts, generateNextManifest, postBuildHook } from './next/index.js';
2
- import '../manifest.schema-B_z3rxRV.js';
1
+ export { ApplySectionCanonicalOverridesOptions, BuildHookOptions, BuildHookResult, BuildScriptsResult, CreateManifestFromDataOptions, CreateManifestFromPagesDirOptions, CreateNextConfigOptions, CreateRobotsLlmsPolicySnippetOptions, CreateSectionManifestOptions, FromNextContentManifestOptions, GenerateBuildScriptsOptions, NextConfigResult, NextContentManifest, NextManifestOptions, SectionFieldMapper, applySectionCanonicalOverrides, createManifestFromData, createManifestFromPagesDir, createNextConfig, createNextPlugin, createRobotsLlmsPolicySnippet, createSectionManifest, extractPagePaths, fromNextContentManifest, generateBuildScripts, generateNextManifest, postBuildHook } from './next/index.js';
2
+ import '../config.schema-CI5OBbhQ.js';
3
3
  import 'zod';
4
+ import '../canonical-from-manifest-BKpEmouS.js';
@@ -2,6 +2,230 @@ import { existsSync } from 'fs';
2
2
  import { readFile, readdir } from 'fs/promises';
3
3
  import { join } from 'path';
4
4
 
5
+ // src/adapters/next/manifest.ts
6
+
7
+ // src/core/normalize/url.ts
8
+ function normalizePath(path, preserveTrailingSlash = false) {
9
+ if (!path || path === "/") {
10
+ return "/";
11
+ }
12
+ const hadTrailingSlash = path.endsWith("/") && path !== "/";
13
+ let normalized = path.startsWith("/") ? path : `/${path}`;
14
+ normalized = normalized.replace(/\/{2,}/g, "/");
15
+ const segments = [];
16
+ const parts = normalized.split("/");
17
+ for (const part of parts) {
18
+ if (part === "." || part === "") {
19
+ continue;
20
+ } else if (part === "..") {
21
+ if (segments.length > 0) {
22
+ segments.pop();
23
+ }
24
+ } else {
25
+ segments.push(part);
26
+ }
27
+ }
28
+ let result = "/" + segments.join("/");
29
+ if (preserveTrailingSlash && hadTrailingSlash && result !== "/") {
30
+ result += "/";
31
+ }
32
+ return result;
33
+ }
34
+ function joinUrlParts(...parts) {
35
+ if (parts.length === 0) {
36
+ return "/";
37
+ }
38
+ const filteredParts = parts.filter((part) => part.length > 0);
39
+ if (filteredParts.length === 0) {
40
+ return "/";
41
+ }
42
+ const joined = filteredParts.map((part) => {
43
+ let p = part.replace(/^\/+/, "");
44
+ p = p.replace(/\/+$/, "");
45
+ return p;
46
+ }).filter((p) => p.length > 0).join("/");
47
+ return joined.length > 0 ? `/${joined}` : "/";
48
+ }
49
+ function normalizeUrl(options) {
50
+ const { baseUrl, path, trailingSlash, stripQuery = true, stripHash = true } = options;
51
+ let parsedBase;
52
+ try {
53
+ parsedBase = new URL(baseUrl);
54
+ } catch {
55
+ throw new TypeError(`Invalid baseUrl: ${baseUrl}`);
56
+ }
57
+ const shouldPreserveTrailingSlash = trailingSlash === "preserve";
58
+ const normalizedPath = normalizePath(path, shouldPreserveTrailingSlash);
59
+ let finalPath = normalizedPath;
60
+ if (trailingSlash === "always") {
61
+ if (!finalPath.endsWith("/")) {
62
+ finalPath = `${finalPath}/`;
63
+ }
64
+ } else if (trailingSlash === "never") {
65
+ if (finalPath !== "/" && finalPath.endsWith("/")) {
66
+ finalPath = finalPath.slice(0, -1);
67
+ }
68
+ }
69
+ const protocol = parsedBase.protocol.toLowerCase();
70
+ let hostname = parsedBase.hostname.toLowerCase();
71
+ let port = parsedBase.port;
72
+ if (port) {
73
+ const isDefaultPort = protocol === "http:" && port === "80" || protocol === "https:" && port === "443";
74
+ if (!isDefaultPort) {
75
+ hostname = `${hostname}:${port}`;
76
+ }
77
+ }
78
+ let fullUrl = `${protocol}//${hostname}${finalPath}`;
79
+ if (!stripQuery && parsedBase.search) {
80
+ fullUrl += parsedBase.search;
81
+ }
82
+ if (!stripHash && parsedBase.hash) {
83
+ fullUrl += parsedBase.hash;
84
+ }
85
+ return fullUrl;
86
+ }
87
+
88
+ // src/core/normalize/sort.ts
89
+ function compareStrings(a, b) {
90
+ return a.localeCompare(b, "en", { sensitivity: "case", numeric: true });
91
+ }
92
+ function sortStrings(items) {
93
+ return [...items].sort(compareStrings);
94
+ }
95
+ function sortBy(items, keyFn) {
96
+ return [...items].sort((a, b) => compareStrings(keyFn(a), keyFn(b)));
97
+ }
98
+ function stableSortStrings(items) {
99
+ return [...items].sort((a, b) => a.localeCompare(b, "en", { sensitivity: "case", numeric: true }));
100
+ }
101
+
102
+ // src/core/canonical/locale.ts
103
+ function selectCanonicalLocale(options) {
104
+ const { defaultLocale, availableLocales } = options;
105
+ if (!availableLocales || availableLocales.length === 0) {
106
+ return null;
107
+ }
108
+ const validLocales = availableLocales.filter(
109
+ (locale) => typeof locale === "string" && locale.length > 0
110
+ );
111
+ if (validLocales.length === 0) {
112
+ return null;
113
+ }
114
+ if (defaultLocale && validLocales.includes(defaultLocale)) {
115
+ return defaultLocale;
116
+ }
117
+ const sorted = stableSortStrings(validLocales);
118
+ return sorted[0] ?? null;
119
+ }
120
+
121
+ // src/core/canonical/canonical-from-manifest.ts
122
+ function buildBaseUrlWithSubdomain(baseUrl, locale, strategy, defaultLocale) {
123
+ if (strategy !== "subdomain" || locale === defaultLocale) {
124
+ return baseUrl;
125
+ }
126
+ try {
127
+ const parsed = new URL(baseUrl);
128
+ return `${parsed.protocol}//${locale}.${parsed.host}`;
129
+ } catch {
130
+ return baseUrl;
131
+ }
132
+ }
133
+ function createCanonicalUrlForItem(item, options) {
134
+ const {
135
+ baseUrl,
136
+ routePrefix,
137
+ defaultLocale,
138
+ trailingSlash,
139
+ localeStrategy,
140
+ routeStyle,
141
+ sectionName,
142
+ sectionPath,
143
+ pathnameFor
144
+ } = options;
145
+ if (item.canonicalOverride && typeof item.canonicalOverride === "string") {
146
+ return item.canonicalOverride;
147
+ }
148
+ const availableLocales = item.locales ?? [defaultLocale];
149
+ const canonicalLocale = selectCanonicalLocale({
150
+ defaultLocale,
151
+ availableLocales
152
+ });
153
+ const locale = canonicalLocale ?? defaultLocale;
154
+ const sectionBase = normalizeSectionPath(sectionPath ?? routePrefix ?? "");
155
+ const effectiveRouteStyle = routeStyle ?? inferRouteStyleFromLocaleStrategy(localeStrategy);
156
+ const normalizedSlug = normalizeItemSlug(item.slug, sectionBase);
157
+ const customPath = effectiveRouteStyle === "custom" ? pathnameFor?.({
158
+ item,
159
+ sectionName: sectionName ?? "",
160
+ slug: normalizedSlug,
161
+ locale,
162
+ defaultLocale,
163
+ sectionPath: sectionBase
164
+ }) : void 0;
165
+ const resolvedPath = customPath ?? buildPathFromRouteStyle({
166
+ routeStyle: effectiveRouteStyle,
167
+ sectionPath: sectionBase,
168
+ slug: normalizedSlug,
169
+ locale,
170
+ defaultLocale
171
+ });
172
+ const effectiveBaseUrl = buildBaseUrlWithSubdomain(baseUrl, locale, localeStrategy, defaultLocale);
173
+ return normalizeUrl({
174
+ baseUrl: effectiveBaseUrl,
175
+ path: resolvedPath,
176
+ trailingSlash,
177
+ stripQuery: true,
178
+ stripHash: true
179
+ });
180
+ }
181
+ function inferRouteStyleFromLocaleStrategy(strategy) {
182
+ if (strategy !== "prefix") {
183
+ return "custom";
184
+ }
185
+ return "prefix";
186
+ }
187
+ function buildPathFromRouteStyle(args) {
188
+ const { routeStyle, sectionPath, slug, locale, defaultLocale } = args;
189
+ const includeLocale = locale !== defaultLocale;
190
+ if (routeStyle === "locale-segment") {
191
+ return joinUrlParts(sectionPath, locale, slug);
192
+ }
193
+ if (routeStyle === "suffix") {
194
+ if (includeLocale) {
195
+ return joinUrlParts(sectionPath, slug, locale);
196
+ }
197
+ return joinUrlParts(sectionPath, slug);
198
+ }
199
+ if (routeStyle === "custom") {
200
+ return joinUrlParts(sectionPath, slug);
201
+ }
202
+ if (includeLocale) {
203
+ return joinUrlParts(locale, sectionPath, slug);
204
+ }
205
+ return joinUrlParts(sectionPath, slug);
206
+ }
207
+ function normalizeSectionPath(sectionPath) {
208
+ if (!sectionPath || sectionPath === "/") {
209
+ return "";
210
+ }
211
+ return sectionPath.startsWith("/") ? sectionPath : `/${sectionPath}`;
212
+ }
213
+ function normalizeItemSlug(slug, sectionPath) {
214
+ const normalizedSlug = slug.startsWith("/") ? slug : `/${slug}`;
215
+ if (!sectionPath) {
216
+ return normalizedSlug;
217
+ }
218
+ if (normalizedSlug === sectionPath) {
219
+ return "/";
220
+ }
221
+ const withSlash = `${sectionPath}/`;
222
+ if (normalizedSlug.startsWith(withSlash)) {
223
+ const relative = normalizedSlug.slice(withSlash.length);
224
+ return relative ? `/${relative}` : "/";
225
+ }
226
+ return normalizedSlug;
227
+ }
228
+
5
229
  // src/adapters/next/manifest.ts
6
230
  function fromNextContentManifest(manifest, options = {}) {
7
231
  const { slugPrefix = "", defaultLocale } = options;
@@ -35,6 +259,132 @@ function fromNextContentManifest(manifest, options = {}) {
35
259
  return result;
36
260
  });
37
261
  }
262
+ function createSectionManifest(options) {
263
+ const {
264
+ items,
265
+ sectionName,
266
+ sectionPath,
267
+ routeStyle,
268
+ defaultLocale,
269
+ defaultLocaleOverride,
270
+ pathnameFor,
271
+ slugKey = "slug",
272
+ localesKey = "locales",
273
+ updatedAtKey = "updatedAt",
274
+ publishedAtKey = "publishedAt",
275
+ priorityKey = "priority",
276
+ canonicalOverrideKey = "canonicalOverride",
277
+ titleFrom = "title",
278
+ descriptionFrom = "description"
279
+ } = options;
280
+ const normalizedItems = items.map((item) => {
281
+ const rawSlug = readStringField(item, slugKey);
282
+ if (!rawSlug) {
283
+ return null;
284
+ }
285
+ const normalized = {
286
+ slug: rawSlug.startsWith("/") ? rawSlug : `/${rawSlug}`
287
+ };
288
+ const locales = readStringArrayField(item, localesKey);
289
+ if (locales.length > 0) {
290
+ normalized.locales = locales;
291
+ } else if (defaultLocale) {
292
+ normalized.locales = [defaultLocale];
293
+ }
294
+ const updatedAt = readStringField(item, updatedAtKey);
295
+ if (updatedAt) {
296
+ normalized.updatedAt = updatedAt;
297
+ }
298
+ const publishedAt = readStringField(item, publishedAtKey);
299
+ if (publishedAt) {
300
+ normalized.publishedAt = publishedAt;
301
+ }
302
+ const priority = readNumberField(item, priorityKey);
303
+ if (priority !== void 0) {
304
+ normalized.priority = priority;
305
+ }
306
+ const canonicalOverride = readStringField(item, canonicalOverrideKey);
307
+ if (canonicalOverride) {
308
+ normalized.canonicalOverride = canonicalOverride;
309
+ }
310
+ const title = readMappedString(item, titleFrom);
311
+ if (title) {
312
+ normalized.title = title;
313
+ }
314
+ const description = readMappedString(item, descriptionFrom);
315
+ if (description) {
316
+ normalized.description = description;
317
+ }
318
+ return normalized;
319
+ }).filter((item) => item !== null);
320
+ const sortedItems = sortBy(normalizedItems, (item) => {
321
+ const locales = item.locales?.join(",") ?? "";
322
+ return `${item.slug}|${locales}`;
323
+ });
324
+ return {
325
+ items: sortedItems,
326
+ ...sectionName && { sectionName },
327
+ ...sectionPath && { sectionPath },
328
+ ...routeStyle && { routeStyle },
329
+ ...defaultLocaleOverride && { defaultLocaleOverride },
330
+ ...pathnameFor && { pathnameFor }
331
+ };
332
+ }
333
+ function applySectionCanonicalOverrides(options) {
334
+ const {
335
+ section,
336
+ baseUrl,
337
+ defaultLocale,
338
+ trailingSlash = "never",
339
+ localeStrategy = "prefix"
340
+ } = options;
341
+ const mappedItems = section.items.map((item) => {
342
+ const canonicalOptions = {
343
+ baseUrl,
344
+ defaultLocale: section.defaultLocaleOverride ?? defaultLocale,
345
+ trailingSlash,
346
+ localeStrategy,
347
+ ...section.routeStyle && { routeStyle: section.routeStyle },
348
+ ...section.sectionPath && {
349
+ sectionPath: section.sectionPath,
350
+ routePrefix: section.sectionPath
351
+ },
352
+ ...section.sectionName && { sectionName: section.sectionName },
353
+ ...section.pathnameFor && { pathnameFor: section.pathnameFor }
354
+ };
355
+ const canonicalOverride = createCanonicalUrlForItem(item, canonicalOptions);
356
+ return {
357
+ ...item,
358
+ canonicalOverride
359
+ };
360
+ });
361
+ return {
362
+ ...section,
363
+ items: mappedItems
364
+ };
365
+ }
366
+ function readStringField(item, key) {
367
+ const value = item[key];
368
+ return typeof value === "string" && value.length > 0 ? value : void 0;
369
+ }
370
+ function readNumberField(item, key) {
371
+ const value = item[key];
372
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
373
+ }
374
+ function readStringArrayField(item, key) {
375
+ const value = item[key];
376
+ if (!Array.isArray(value)) {
377
+ return [];
378
+ }
379
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
380
+ }
381
+ function readMappedString(item, mapper) {
382
+ if (typeof mapper === "function") {
383
+ const value = mapper(item);
384
+ return typeof value === "string" && value.length > 0 ? value : void 0;
385
+ }
386
+ return readStringField(item, mapper);
387
+ }
38
388
  function parseFrontmatter(content) {
39
389
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
40
390
  const match = content.match(frontmatterRegex);
@@ -222,17 +572,6 @@ function generateNextManifest(options, buildManifest) {
222
572
  };
223
573
  }
224
574
 
225
- // src/core/normalize/sort.ts
226
- function compareStrings(a, b) {
227
- return a.localeCompare(b, "en", { sensitivity: "case", numeric: true });
228
- }
229
- function sortStrings(items) {
230
- return [...items].sort(compareStrings);
231
- }
232
- function sortBy(items, keyFn) {
233
- return [...items].sort((a, b) => compareStrings(keyFn(a), keyFn(b)));
234
- }
235
-
236
575
  // src/core/normalize/text.ts
237
576
  function normalizeLineEndings(text, lineEndings) {
238
577
  const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -396,7 +735,7 @@ function generateLlmsTxt(manifest, _options) {
396
735
 
397
736
  // src/core/generate/llms-full-txt.ts
398
737
  function createLlmsFullTxt(options) {
399
- const { config, canonicalUrls, manifestItems } = options;
738
+ const { config, canonicalUrls, manifestItems, manifestEntries } = options;
400
739
  const lineEndings = config.format?.lineEndings ?? "lf";
401
740
  const lines = [];
402
741
  lines.push(`# ${config.brand.name} - Full LLM Context`);
@@ -413,6 +752,10 @@ function createLlmsFullTxt(options) {
413
752
  lines.push(`Organization: ${config.brand.org}`);
414
753
  }
415
754
  lines.push(`Locales: ${config.brand.locales.join(", ")}`);
755
+ const lastUpdated = getLastUpdatedDate(manifestItems);
756
+ if (lastUpdated) {
757
+ lines.push(`Last Updated: ${lastUpdated}`);
758
+ }
416
759
  lines.push("");
417
760
  if (canonicalUrls.length > 0) {
418
761
  lines.push("## All Canonical URLs");
@@ -450,11 +793,16 @@ function createLlmsFullTxt(options) {
450
793
  lines.push("");
451
794
  }
452
795
  }
453
- const hasSocial = config.contact?.social?.twitter || config.contact?.social?.linkedin || config.contact?.social?.github;
454
- const hasBooking = config.booking?.url;
455
- if (hasSocial || hasBooking) {
456
- lines.push("## Social & Booking");
796
+ const hasContact = config.contact?.email || config.contact?.phone || config.contact?.social?.twitter || config.contact?.social?.linkedin || config.contact?.social?.github || config.booking?.url;
797
+ if (hasContact) {
798
+ lines.push("## Contact");
457
799
  lines.push("");
800
+ if (config.contact?.email) {
801
+ lines.push(`- Email: ${config.contact.email}`);
802
+ }
803
+ if (config.contact?.phone) {
804
+ lines.push(`- Phone: ${config.contact.phone}`);
805
+ }
458
806
  if (config.contact?.social?.twitter) {
459
807
  lines.push(`- Twitter: ${config.contact.social.twitter}`);
460
808
  }
@@ -498,7 +846,16 @@ function createLlmsFullTxt(options) {
498
846
  lines.push(`- [${hub}](${hub}) - ${getHubLabel2(hub)}`);
499
847
  }
500
848
  }
501
- if (manifestItems.length > 0) {
849
+ if (manifestEntries && manifestEntries.length > 0) {
850
+ const sortedEntries = sortBy(manifestEntries, (entry) => {
851
+ return `${entry.item.slug}|${entry.canonicalUrl}`;
852
+ });
853
+ for (const entry of sortedEntries) {
854
+ const title = entry.item.title ?? entry.item.slug;
855
+ const locales = entry.item.locales?.join(", ") ?? config.brand.locales[0] ?? "en";
856
+ lines.push(`- [${title}](${entry.canonicalUrl}) (${locales})`);
857
+ }
858
+ } else if (manifestItems.length > 0) {
502
859
  const sortedItems = sortBy(manifestItems, (item) => item.slug);
503
860
  for (const item of sortedItems) {
504
861
  const url = item.canonicalOverride ?? `${config.site.baseUrl}${item.slug}`;
@@ -519,6 +876,24 @@ function createLlmsFullTxt(options) {
519
876
  lineCount: finalLines.length
520
877
  };
521
878
  }
879
+ function getLastUpdatedDate(items) {
880
+ const timestamps = [];
881
+ for (const item of items) {
882
+ if (item.updatedAt) {
883
+ timestamps.push(item.updatedAt);
884
+ } else if (item.publishedAt) {
885
+ timestamps.push(item.publishedAt);
886
+ }
887
+ }
888
+ if (timestamps.length === 0) {
889
+ return null;
890
+ }
891
+ const latest = timestamps.map((value) => new Date(value)).filter((value) => !Number.isNaN(value.getTime())).sort((a, b) => b.getTime() - a.getTime())[0];
892
+ if (!latest) {
893
+ return null;
894
+ }
895
+ return latest.toISOString().slice(0, 10);
896
+ }
522
897
  function getHubLabel2(hub) {
523
898
  const labels = {
524
899
  "/services": "Services overview",
@@ -668,6 +1043,6 @@ function createNextPlugin() {
668
1043
  };
669
1044
  }
670
1045
 
671
- export { createManifestFromData, createManifestFromPagesDir, createNextConfig, createNextPlugin, createRobotsLlmsPolicySnippet, extractPagePaths, fromNextContentManifest, generateBuildScripts, generateNextManifest, postBuildHook };
1046
+ export { applySectionCanonicalOverrides, createManifestFromData, createManifestFromPagesDir, createNextConfig, createNextPlugin, createRobotsLlmsPolicySnippet, createSectionManifest, extractPagePaths, fromNextContentManifest, generateBuildScripts, generateNextManifest, postBuildHook };
672
1047
  //# sourceMappingURL=index.js.map
673
1048
  //# sourceMappingURL=index.js.map