@jmp-technologies/analytics 0.1.26 → 0.1.30

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.
Files changed (86) hide show
  1. package/README.md +43 -0
  2. package/bin/jmp-init.mjs +34 -0
  3. package/dist/astro/client.cjs +227 -10
  4. package/dist/astro/client.cjs.map +1 -1
  5. package/dist/astro/client.d.cts +2 -2
  6. package/dist/astro/client.d.ts +2 -2
  7. package/dist/astro/client.js +2 -2
  8. package/dist/astro/content.cjs +340 -14
  9. package/dist/astro/content.cjs.map +1 -1
  10. package/dist/astro/content.d.cts +16 -3
  11. package/dist/astro/content.d.ts +16 -3
  12. package/dist/astro/content.js +11 -3
  13. package/dist/astro/integration.cjs +229 -11
  14. package/dist/astro/integration.cjs.map +1 -1
  15. package/dist/astro/integration.js +3 -3
  16. package/dist/astro/middleware.cjs +2 -1
  17. package/dist/astro/middleware.cjs.map +1 -1
  18. package/dist/astro/middleware.d.cts +2 -2
  19. package/dist/astro/middleware.d.ts +2 -2
  20. package/dist/astro/middleware.js +2 -2
  21. package/dist/astro/register.cjs +229 -11
  22. package/dist/astro/register.cjs.map +1 -1
  23. package/dist/astro/register.js +3 -3
  24. package/dist/{chunk-A4RRTY5D.js → chunk-AIFJ7DVO.js} +3 -2
  25. package/dist/{chunk-A4RRTY5D.js.map → chunk-AIFJ7DVO.js.map} +1 -1
  26. package/dist/{chunk-TFHOTBOL.js → chunk-FS4BZ7DR.js} +66 -6
  27. package/dist/chunk-FS4BZ7DR.js.map +1 -0
  28. package/dist/{chunk-RCOHQAGL.js → chunk-GTPKRBFR.js} +284 -12
  29. package/dist/chunk-GTPKRBFR.js.map +1 -0
  30. package/dist/{chunk-HODG4NJQ.js → chunk-HFLU7HQE.js} +2 -2
  31. package/dist/{chunk-WFTTIRDJ.js → chunk-LLTB7ZFC.js} +2 -2
  32. package/dist/{chunk-PKGUBYZR.js → chunk-LTPLLPOR.js} +3 -3
  33. package/dist/{chunk-XQZOGWF2.js → chunk-ZEC22LFP.js} +3 -3
  34. package/dist/{create-jmp-analytics-JKAsJAWb.d.ts → create-jmp-analytics-BeuuU6as.d.ts} +1 -1
  35. package/dist/{create-jmp-analytics-Mau-oxji.d.cts → create-jmp-analytics-Ce2CrhhR.d.cts} +1 -1
  36. package/dist/index.cjs +289 -11
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +16 -7
  39. package/dist/index.d.ts +16 -7
  40. package/dist/index.js +12 -2
  41. package/dist/next/content.cjs +343 -14
  42. package/dist/next/content.cjs.map +1 -1
  43. package/dist/next/content.d.cts +8 -5
  44. package/dist/next/content.d.ts +8 -5
  45. package/dist/next/content.js +14 -3
  46. package/dist/next/content.js.map +1 -1
  47. package/dist/next/instrumentation.cjs +229 -11
  48. package/dist/next/instrumentation.cjs.map +1 -1
  49. package/dist/next/instrumentation.js +2 -2
  50. package/dist/next/landing-pages.cjs +875 -0
  51. package/dist/next/landing-pages.cjs.map +1 -0
  52. package/dist/next/landing-pages.d.cts +103 -0
  53. package/dist/next/landing-pages.d.ts +103 -0
  54. package/dist/next/landing-pages.js +297 -0
  55. package/dist/next/landing-pages.js.map +1 -0
  56. package/dist/next/middleware.cjs +2 -1
  57. package/dist/next/middleware.cjs.map +1 -1
  58. package/dist/next/middleware.d.cts +2 -2
  59. package/dist/next/middleware.d.ts +2 -2
  60. package/dist/next/middleware.js +2 -2
  61. package/dist/next/site-root-server.cjs +229 -11
  62. package/dist/next/site-root-server.cjs.map +1 -1
  63. package/dist/next/site-root-server.js +4 -4
  64. package/dist/next.cjs +227 -10
  65. package/dist/next.cjs.map +1 -1
  66. package/dist/next.d.cts +3 -3
  67. package/dist/next.d.ts +3 -3
  68. package/dist/next.js +4 -4
  69. package/dist/react.cjs +227 -10
  70. package/dist/react.cjs.map +1 -1
  71. package/dist/react.d.cts +2 -2
  72. package/dist/react.d.ts +2 -2
  73. package/dist/react.js +3 -3
  74. package/dist/{site-config-CXUdGLqw.d.cts → site-config-B4uEj4xX.d.ts} +22 -2
  75. package/dist/{site-config-w_Db9YSk.d.ts → site-config-DIFmnwqv.d.cts} +22 -2
  76. package/dist/{track-page-view-CkuSYuco.d.cts → track-page-view-Bv0BAvxG.d.cts} +1 -1
  77. package/dist/{track-page-view-QRSk8vap.d.ts → track-page-view-CkgIp9-j.d.ts} +1 -1
  78. package/dist/{types-C1eq8E5u.d.ts → types-B2iP79lb.d.cts} +99 -2
  79. package/dist/{types-C1eq8E5u.d.cts → types-B2iP79lb.d.ts} +99 -2
  80. package/package.json +11 -2
  81. package/dist/chunk-RCOHQAGL.js.map +0 -1
  82. package/dist/chunk-TFHOTBOL.js.map +0 -1
  83. /package/dist/{chunk-HODG4NJQ.js.map → chunk-HFLU7HQE.js.map} +0 -0
  84. /package/dist/{chunk-WFTTIRDJ.js.map → chunk-LLTB7ZFC.js.map} +0 -0
  85. /package/dist/{chunk-PKGUBYZR.js.map → chunk-LTPLLPOR.js.map} +0 -0
  86. /package/dist/{chunk-XQZOGWF2.js.map → chunk-ZEC22LFP.js.map} +0 -0
@@ -0,0 +1,875 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next/landing-pages.tsx
31
+ var landing_pages_exports = {};
32
+ __export(landing_pages_exports, {
33
+ JMP_LANDING_HUB: () => JMP_LANDING_HUB,
34
+ JmpLandingDetailPage: () => JmpLandingDetailPage,
35
+ JmpLandingIndexPage: () => JmpLandingIndexPage,
36
+ absoluteJmpSiteUrl: () => absoluteJmpSiteUrl,
37
+ buildJmpLandingIndexMetadata: () => buildJmpLandingIndexMetadata,
38
+ buildJmpLandingMetadata: () => buildJmpLandingMetadata,
39
+ buildJmpLandingSitemapRoutes: () => buildJmpLandingSitemapRoutes,
40
+ filterJmpLandingPagesForDefaultLocale: () => filterJmpLandingPagesForDefaultLocale,
41
+ filterJmpLandingPagesForLocalizedRoutes: () => filterJmpLandingPagesForLocalizedRoutes,
42
+ generateJmpLandingStaticParams: () => generateJmpLandingStaticParams,
43
+ generateJmpLocalizedLandingStaticParams: () => generateJmpLocalizedLandingStaticParams,
44
+ jmpBreadcrumbListJsonLd: () => jmpBreadcrumbListJsonLd,
45
+ jmpLandingKindSegment: () => jmpLandingKindSegment,
46
+ jmpLocalizedLandingStaticParams: () => jmpLocalizedLandingStaticParams,
47
+ plainTextFromMarkdown: () => plainTextFromMarkdown,
48
+ resolveJmpSiteUrl: () => resolveJmpSiteUrl
49
+ });
50
+ module.exports = __toCommonJS(landing_pages_exports);
51
+ var import_link = __toESM(require("next/link"), 1);
52
+ var import_navigation = require("next/navigation");
53
+
54
+ // src/landing-capture.ts
55
+ var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
56
+
57
+ // src/constants.ts
58
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.26";
59
+
60
+ // src/http.ts
61
+ function responseLooksLikeHtml(body) {
62
+ const s = body.trimStart();
63
+ return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
64
+ }
65
+ function normalizeBaseUrl(baseUrl) {
66
+ return baseUrl.replace(/\/$/, "");
67
+ }
68
+
69
+ // src/content/manifest.ts
70
+ var JMP_CONTENT_MANIFEST_VERSION = 1;
71
+ var TRUST_SECTIONS = /* @__PURE__ */ new Set([
72
+ "summary",
73
+ "testimonials",
74
+ "reviews",
75
+ "stats",
76
+ "logos",
77
+ "credentials"
78
+ ]);
79
+ function normalizeKey(value) {
80
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
81
+ }
82
+ function isValidExtraKey(key) {
83
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key);
84
+ }
85
+ var MAX_EXTERNAL_ADMIN_LINKS = 10;
86
+ var MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80;
87
+ var EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
88
+ function tryNormalizeHttpUrl(input) {
89
+ if (typeof input !== "string" || !input.trim()) {
90
+ return null;
91
+ }
92
+ try {
93
+ const url = new URL(input.trim());
94
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
95
+ return null;
96
+ }
97
+ return url.toString().replace(/\/$/, "");
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function normalizeShopifyExternalSource(shopify) {
103
+ if (!shopify || typeof shopify !== "object") {
104
+ return null;
105
+ }
106
+ const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl);
107
+ const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl);
108
+ if (!adminUrl && !storeUrl) {
109
+ return null;
110
+ }
111
+ return {
112
+ ...adminUrl ? { adminUrl } : {},
113
+ ...storeUrl ? { storeUrl } : {}
114
+ };
115
+ }
116
+ function normalizeExternalAdminLinkEntries(links) {
117
+ if (!Array.isArray(links)) {
118
+ return [];
119
+ }
120
+ const seenIds = /* @__PURE__ */ new Set();
121
+ const out = [];
122
+ for (const raw of links) {
123
+ if (!raw || typeof raw !== "object" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {
124
+ continue;
125
+ }
126
+ const id = typeof raw.id === "string" ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") : "";
127
+ const label = typeof raw.label === "string" ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH) : "";
128
+ const url = tryNormalizeHttpUrl(raw.url);
129
+ if (!id || !label || !url || !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) || seenIds.has(id)) {
130
+ continue;
131
+ }
132
+ seenIds.add(id);
133
+ out.push({ id, label, url });
134
+ }
135
+ return out;
136
+ }
137
+ function normalizeExternalSources(externalSources) {
138
+ if (!externalSources || typeof externalSources !== "object") {
139
+ return null;
140
+ }
141
+ const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl);
142
+ const shopify = normalizeShopifyExternalSource(externalSources.shopify);
143
+ const links = normalizeExternalAdminLinkEntries(externalSources.links);
144
+ if (!studioUrl && !shopify && links.length === 0) {
145
+ return null;
146
+ }
147
+ return {
148
+ ...studioUrl ? { sanity: { studioUrl } } : {},
149
+ ...shopify ? { shopify } : {},
150
+ ...links.length > 0 ? { links } : {}
151
+ };
152
+ }
153
+ function normalizeJmpContentManifest(input) {
154
+ if (!input || typeof input !== "object") {
155
+ return null;
156
+ }
157
+ const routes = normalizeRoutes(input.routes);
158
+ const externalSources = normalizeExternalSources(input.externalSources);
159
+ const trust = normalizeTrust(input.trust);
160
+ const contact = normalizeContact(input.contact);
161
+ if (routes.length === 0 && !externalSources && !trust && !contact) {
162
+ return null;
163
+ }
164
+ return {
165
+ version: JMP_CONTENT_MANIFEST_VERSION,
166
+ ...routes.length > 0 ? { routes } : {},
167
+ ...externalSources ? { externalSources } : {},
168
+ ...trust ? { trust } : {},
169
+ ...contact ? { contact } : {}
170
+ };
171
+ }
172
+ function normalizeRoutes(routes) {
173
+ if (!Array.isArray(routes)) {
174
+ return [];
175
+ }
176
+ const seen = /* @__PURE__ */ new Set();
177
+ const out = [];
178
+ for (const raw of routes) {
179
+ if (!raw || typeof raw !== "object") continue;
180
+ const wrapper = typeof raw.wrapper === "string" ? raw.wrapper.trim().toLowerCase() : "";
181
+ const pattern = typeof raw.pattern === "string" ? raw.pattern.trim() : "";
182
+ if (!wrapper || !pattern) continue;
183
+ const dedupe = `${wrapper}:${pattern}`;
184
+ if (seen.has(dedupe)) continue;
185
+ seen.add(dedupe);
186
+ out.push({
187
+ wrapper,
188
+ pattern,
189
+ ...typeof raw.index === "string" && raw.index.trim() ? { index: raw.index.trim() } : {},
190
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
191
+ });
192
+ }
193
+ return out;
194
+ }
195
+ function normalizeTrust(trust) {
196
+ if (!trust || typeof trust !== "object") return null;
197
+ const blocks = normalizeTrustBlocks(trust.blocks);
198
+ const labels = normalizeTrustLabels(trust.labels);
199
+ const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true;
200
+ if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null;
201
+ return {
202
+ ...blocks.length > 0 ? { blocks } : {},
203
+ ...labels ? { labels } : {},
204
+ ...mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}
205
+ };
206
+ }
207
+ function normalizeTrustBlocks(blocks) {
208
+ if (!Array.isArray(blocks)) return [];
209
+ const seen = /* @__PURE__ */ new Set();
210
+ const out = [];
211
+ for (const raw of blocks) {
212
+ if (!raw || typeof raw !== "object") continue;
213
+ const section = typeof raw.section === "string" ? raw.section.trim().toLowerCase() : "";
214
+ if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue;
215
+ seen.add(section);
216
+ out.push({
217
+ section,
218
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
219
+ });
220
+ }
221
+ return out;
222
+ }
223
+ function normalizeTrustLabels(labels) {
224
+ if (!labels || typeof labels !== "object") return null;
225
+ const out = {};
226
+ for (const [key, value] of Object.entries(labels)) {
227
+ if (TRUST_SECTIONS.has(key) && typeof value === "string" && value.trim()) {
228
+ out[key] = value.trim();
229
+ }
230
+ }
231
+ return Object.keys(out).length > 0 ? out : null;
232
+ }
233
+ function normalizeContact(contact) {
234
+ if (!contact || typeof contact !== "object") return null;
235
+ const extras = normalizeContactExtras(contact.extras);
236
+ return extras.length > 0 ? { extras } : null;
237
+ }
238
+ function normalizeContactExtras(extras) {
239
+ if (!Array.isArray(extras)) return [];
240
+ const seen = /* @__PURE__ */ new Set();
241
+ const out = [];
242
+ for (const raw of extras) {
243
+ if (!raw || typeof raw !== "object") continue;
244
+ const label = typeof raw.label === "string" ? raw.label.trim() : "";
245
+ const key = normalizeKey(
246
+ typeof raw.key === "string" && raw.key.trim() ? raw.key : label
247
+ );
248
+ if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue;
249
+ seen.add(key);
250
+ out.push({ key, label });
251
+ }
252
+ return out;
253
+ }
254
+ function stableJmpManifestFingerprint(manifest) {
255
+ return manifest ? JSON.stringify(manifest) : "";
256
+ }
257
+ function inferWrapperSlotsFromManifest(manifest) {
258
+ const slots = /* @__PURE__ */ new Set();
259
+ for (const route of manifest?.routes ?? []) {
260
+ slots.add(route.wrapper);
261
+ }
262
+ for (const block of manifest?.trust?.blocks ?? []) {
263
+ slots.add(`trust.${block.section}`);
264
+ slots.add("trust");
265
+ }
266
+ if (manifest?.contact?.extras?.length) {
267
+ slots.add("contact");
268
+ }
269
+ return [...slots];
270
+ }
271
+
272
+ // src/content/fetch.ts
273
+ async function getContent(config, path) {
274
+ const text = await getContentText(config, path);
275
+ try {
276
+ return JSON.parse(text);
277
+ } catch {
278
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
279
+ const url = `${baseUrl}${path}`;
280
+ throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
281
+ }
282
+ }
283
+ async function getContentText(config, path) {
284
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
285
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
286
+ if (!resolvedFetch) {
287
+ throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
288
+ }
289
+ const url = `${baseUrl}${path}`;
290
+ if (config.debug) {
291
+ console.debug("[@jmp-technologies/analytics]", "GET", url);
292
+ }
293
+ const res = await resolvedFetch(url, {
294
+ method: "GET",
295
+ headers: {
296
+ "X-JMP-Tracking-Key": config.trackingKey
297
+ }
298
+ });
299
+ const text = await res.text();
300
+ if (!res.ok) {
301
+ if (responseLooksLikeHtml(text)) {
302
+ throw new Error(
303
+ `JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
304
+ );
305
+ }
306
+ let parsed = {};
307
+ try {
308
+ parsed = JSON.parse(text);
309
+ } catch {
310
+ parsed = { error: text };
311
+ }
312
+ throw new Error(
313
+ [`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
314
+ );
315
+ }
316
+ return text;
317
+ }
318
+
319
+ // src/content/wrapper-discovery.ts
320
+ var WRAPPER_SLOT_ORDER = [
321
+ "blog",
322
+ "faq",
323
+ "contact",
324
+ "trust",
325
+ "trust.summary",
326
+ "trust.testimonials",
327
+ "trust.reviews",
328
+ "trust.stats",
329
+ "trust.logos",
330
+ "trust.credentials"
331
+ ];
332
+ var discovered = /* @__PURE__ */ new Set();
333
+ var syncTimer = null;
334
+ var pendingSyncConfig = null;
335
+ var reportedDedupeKeys = /* @__PURE__ */ new Set();
336
+ var syncHandler = null;
337
+ function markContentWrapper(slot) {
338
+ const normalized = slot.trim().toLowerCase();
339
+ if (!normalized) {
340
+ return;
341
+ }
342
+ discovered.add(normalized);
343
+ }
344
+ function getDiscoveredContentWrappers() {
345
+ return sortWrapperSlots([...discovered]);
346
+ }
347
+ function resolveContentManifestForReport(config) {
348
+ return normalizeJmpContentManifest(config.contentManifest);
349
+ }
350
+ function resolveContentWrappersForReport(config) {
351
+ const explicit = config.contentWrappers ?? [];
352
+ const auto = getDiscoveredContentWrappers();
353
+ const fromManifest = inferWrapperSlotsFromManifest(
354
+ resolveContentManifestForReport(config)
355
+ );
356
+ if (config.contentWrapperMode === "manual") {
357
+ return sortWrapperSlots([...explicit, ...fromManifest]);
358
+ }
359
+ if (config.contentWrapperMode === "explicit-only") {
360
+ return sortWrapperSlots([...explicit, ...fromManifest]);
361
+ }
362
+ const merged = /* @__PURE__ */ new Set([...explicit, ...auto, ...fromManifest]);
363
+ return sortWrapperSlots([...merged]);
364
+ }
365
+ function hasReportedContentWrappers(dedupeKey) {
366
+ return reportedDedupeKeys.has(dedupeKey);
367
+ }
368
+ function rememberReportedContentWrappers(dedupeKey) {
369
+ reportedDedupeKeys.add(dedupeKey);
370
+ }
371
+ function setContentWrapperSyncHandler(handler) {
372
+ syncHandler = handler;
373
+ }
374
+ function scheduleContentWrapperSync(config) {
375
+ if (!syncHandler) {
376
+ return;
377
+ }
378
+ pendingSyncConfig = config;
379
+ if (syncTimer) {
380
+ clearTimeout(syncTimer);
381
+ }
382
+ syncTimer = setTimeout(() => {
383
+ syncTimer = null;
384
+ const cfg = pendingSyncConfig;
385
+ pendingSyncConfig = null;
386
+ if (!cfg || !syncHandler) {
387
+ return;
388
+ }
389
+ void syncHandler(cfg).catch(() => {
390
+ });
391
+ }, 750);
392
+ }
393
+ function sortWrapperSlots(slots) {
394
+ const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]));
395
+ return [...new Set(slots)].sort((a, b) => {
396
+ const ai = order.get(a) ?? 999;
397
+ const bi = order.get(b) ?? 999;
398
+ if (ai !== bi) {
399
+ return ai - bi;
400
+ }
401
+ return a.localeCompare(b);
402
+ });
403
+ }
404
+
405
+ // src/content/api.ts
406
+ function afterContentWrapperUse(config) {
407
+ scheduleContentWrapperSync(config);
408
+ }
409
+ async function getJmpLandingPages(config, filters) {
410
+ const params = new URLSearchParams();
411
+ if (filters?.kind) {
412
+ params.set("kind", filters.kind);
413
+ }
414
+ if (filters?.locale) {
415
+ params.set("locale", filters.locale);
416
+ }
417
+ const query = params.toString();
418
+ const data = await getContent(
419
+ config,
420
+ `/api/content/landing-pages${query ? `?${query}` : ""}`
421
+ );
422
+ markContentWrapper("landing");
423
+ afterContentWrapperUse(config);
424
+ return data.pages;
425
+ }
426
+ async function getJmpLandingPageDetail(config, kind, slug, locale) {
427
+ const params = new URLSearchParams();
428
+ if (locale) {
429
+ params.set("locale", locale);
430
+ }
431
+ const query = params.toString();
432
+ try {
433
+ const detail = await getContent(
434
+ config,
435
+ `/api/content/landing-pages/${encodeURIComponent(kind)}/${encodeURIComponent(slug)}${query ? `?${query}` : ""}`
436
+ );
437
+ markContentWrapper("landing");
438
+ afterContentWrapperUse(config);
439
+ return detail;
440
+ } catch (error) {
441
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
442
+ return null;
443
+ }
444
+ throw error;
445
+ }
446
+ }
447
+ async function registerJmpContentWrappers(config) {
448
+ const wrappers = resolveContentWrappersForReport(config);
449
+ if (!wrappers.length) {
450
+ return { ok: false, skipped: true };
451
+ }
452
+ const manifest = resolveContentManifestForReport(config);
453
+ const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}:${stableJmpManifestFingerprint(manifest)}`;
454
+ if (hasReportedContentWrappers(dedupeKey)) {
455
+ return { ok: true, wrappers };
456
+ }
457
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
458
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
459
+ if (!resolvedFetch) {
460
+ throw new Error(
461
+ "JMP content wrappers: fetch is not available; pass fetchImpl in config"
462
+ );
463
+ }
464
+ const url = `${baseUrl}/api/content/capabilities`;
465
+ if (config.debug) {
466
+ console.debug("[@jmp-technologies/analytics]", "POST", url, {
467
+ wrappers,
468
+ manifest
469
+ });
470
+ }
471
+ const body = {
472
+ wrappers,
473
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
474
+ };
475
+ if (manifest) {
476
+ body.manifest = manifest;
477
+ }
478
+ const res = await resolvedFetch(url, {
479
+ method: "POST",
480
+ headers: {
481
+ "Content-Type": "application/json",
482
+ "X-JMP-Tracking-Key": config.trackingKey
483
+ },
484
+ body: JSON.stringify(body)
485
+ });
486
+ const text = await res.text();
487
+ if (!res.ok) {
488
+ let parsed = {};
489
+ try {
490
+ parsed = JSON.parse(text);
491
+ } catch {
492
+ parsed = { error: text };
493
+ }
494
+ throw new Error(
495
+ [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
496
+ );
497
+ }
498
+ rememberReportedContentWrappers(dedupeKey);
499
+ const data = JSON.parse(text);
500
+ return { ok: true, wrappers: data.wrappers ?? wrappers };
501
+ }
502
+ setContentWrapperSyncHandler(registerJmpContentWrappers);
503
+
504
+ // src/env-public.ts
505
+ function firstDefined(...values) {
506
+ for (const v of values) {
507
+ if (v) return v;
508
+ }
509
+ return void 0;
510
+ }
511
+ function readJmpPublicEnvFromProcessEnv() {
512
+ if (typeof process === "undefined" || !process.env) {
513
+ return {};
514
+ }
515
+ const { env } = process;
516
+ const analyticsUrl = firstDefined(
517
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
518
+ env.PUBLIC_JMP_ANALYTICS_URL
519
+ );
520
+ const trackingKey = firstDefined(
521
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
522
+ env.PUBLIC_JMP_TRACKING_KEY
523
+ );
524
+ const requireConsentRaw = firstDefined(
525
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
526
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
527
+ );
528
+ const autoContactRaw = firstDefined(
529
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
530
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
531
+ );
532
+ return {
533
+ analyticsUrl,
534
+ trackingKey,
535
+ requireConsent: requireConsentRaw === "true",
536
+ autoContactLinks: autoContactRaw === "true"
537
+ };
538
+ }
539
+ function readJmpPublicEnvFromProcess(overrides) {
540
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
541
+ return {
542
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
543
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
544
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
545
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
546
+ };
547
+ }
548
+
549
+ // src/site-config.ts
550
+ var registeredSite = null;
551
+ function getJmpSiteDefinition() {
552
+ return registeredSite;
553
+ }
554
+ function getJmpContentConfig() {
555
+ const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
556
+ if (!analyticsUrl || !trackingKey) {
557
+ return null;
558
+ }
559
+ const site = getJmpSiteDefinition();
560
+ return {
561
+ baseUrl: analyticsUrl,
562
+ trackingKey,
563
+ contentWrappers: site?.contentWrappers ?? void 0,
564
+ contentManifest: site?.contentManifest ?? void 0
565
+ };
566
+ }
567
+
568
+ // src/content/site-content.ts
569
+ async function loadLandingPages(filters) {
570
+ const c = getJmpContentConfig();
571
+ if (!c) {
572
+ return [];
573
+ }
574
+ try {
575
+ return await getJmpLandingPages(c, filters);
576
+ } catch {
577
+ return [];
578
+ }
579
+ }
580
+ async function loadLandingPageDetail(kind, slug, locale) {
581
+ const c = getJmpContentConfig();
582
+ if (!c) {
583
+ return null;
584
+ }
585
+ try {
586
+ return await getJmpLandingPageDetail(c, kind, slug, locale);
587
+ } catch {
588
+ return null;
589
+ }
590
+ }
591
+
592
+ // src/landing-pages/hub.ts
593
+ var JMP_LANDING_HUB = {
594
+ location: {
595
+ title: "Areas we serve",
596
+ path: "/areas",
597
+ description: "Local pages for cities and regions we work in.",
598
+ breadcrumb: "Areas",
599
+ segment: "areas"
600
+ },
601
+ service: {
602
+ title: "Services",
603
+ path: "/services",
604
+ description: "What we offer and how we help.",
605
+ breadcrumb: "Services",
606
+ segment: "services"
607
+ }
608
+ };
609
+ function jmpLandingKindSegment(kind) {
610
+ return JMP_LANDING_HUB[kind].segment;
611
+ }
612
+
613
+ // src/landing-pages/utils.ts
614
+ function plainTextFromMarkdown(input, maxLength = 5e3) {
615
+ return input.replace(/[#*_`[\]]/g, "").trim().slice(0, maxLength);
616
+ }
617
+ function resolveJmpSiteUrl(override) {
618
+ const fromArg = override?.trim().replace(/\/$/, "");
619
+ if (fromArg) return fromArg;
620
+ const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\/$/, "");
621
+ return fromConfig || null;
622
+ }
623
+ function absoluteJmpSiteUrl(path, siteUrl) {
624
+ const base = resolveJmpSiteUrl(siteUrl);
625
+ if (!base) {
626
+ return path.startsWith("/") ? path : `/${path}`;
627
+ }
628
+ const normalized = path.startsWith("/") ? path : `/${path}`;
629
+ return `${base}${normalized}`;
630
+ }
631
+ function filterJmpLandingPagesForDefaultLocale(pages, kind) {
632
+ const segment = jmpLandingKindSegment(kind);
633
+ return pages.filter((page) => {
634
+ const parts = page.path.split("/").filter(Boolean);
635
+ return parts[0] === segment;
636
+ });
637
+ }
638
+ function filterJmpLandingPagesForLocalizedRoutes(pages, kind) {
639
+ const segment = jmpLandingKindSegment(kind);
640
+ return pages.filter((page) => {
641
+ const parts = page.path.split("/").filter(Boolean);
642
+ return parts.length >= 3 && parts[1] === segment;
643
+ });
644
+ }
645
+ function jmpLocalizedLandingStaticParams(pages, kind) {
646
+ return filterJmpLandingPagesForLocalizedRoutes(pages, kind).map((page) => {
647
+ const [locale, , slug] = page.path.split("/").filter(Boolean);
648
+ return { locale, slug };
649
+ });
650
+ }
651
+
652
+ // src/landing-pages/json-ld.ts
653
+ function jmpBreadcrumbListJsonLd(items, siteUrl) {
654
+ return {
655
+ "@context": "https://schema.org",
656
+ "@type": "BreadcrumbList",
657
+ itemListElement: items.map((item, index) => ({
658
+ "@type": "ListItem",
659
+ position: index + 1,
660
+ name: item.name,
661
+ item: absoluteJmpSiteUrl(item.path, siteUrl)
662
+ }))
663
+ };
664
+ }
665
+
666
+ // src/landing-pages/static-params.ts
667
+ async function generateJmpLandingStaticParams(kind) {
668
+ const pages = await loadLandingPages({ kind });
669
+ return filterJmpLandingPagesForDefaultLocale(pages, kind).map((page) => ({
670
+ slug: page.slug
671
+ }));
672
+ }
673
+ async function generateJmpLocalizedLandingStaticParams(kind) {
674
+ const pages = await loadLandingPages({ kind });
675
+ return jmpLocalizedLandingStaticParams(pages, kind);
676
+ }
677
+
678
+ // src/landing-pages/sitemap.ts
679
+ function buildJmpLandingSitemapRoutes(entries, options) {
680
+ const { siteUrl, changeFrequency = "monthly" } = options;
681
+ const defaultPriority = {
682
+ location: 0.7,
683
+ service: 0.68
684
+ };
685
+ return entries.map((entry) => {
686
+ const priority = options.priorityByKind?.[entry.kind] ?? defaultPriority[entry.kind];
687
+ const languages = entry.alternates.length > 0 ? Object.fromEntries(
688
+ entry.alternates.map((alt) => [
689
+ alt.locale,
690
+ absoluteJmpSiteUrl(alt.path, siteUrl)
691
+ ])
692
+ ) : void 0;
693
+ return {
694
+ url: absoluteJmpSiteUrl(entry.path, siteUrl),
695
+ lastModified: entry.updated_at ? new Date(entry.updated_at) : void 0,
696
+ changeFrequency,
697
+ priority,
698
+ alternates: languages ? { languages } : void 0
699
+ };
700
+ });
701
+ }
702
+
703
+ // src/next/landing-pages.tsx
704
+ var import_jsx_runtime = require("react/jsx-runtime");
705
+ async function buildJmpLandingMetadata({
706
+ kind,
707
+ slug,
708
+ locale,
709
+ siteUrl
710
+ }) {
711
+ const detail = await loadLandingPageDetail(kind, slug, locale);
712
+ if (!detail) {
713
+ return { title: "Not found" };
714
+ }
715
+ const { page, alternates } = detail;
716
+ const title = page.seo_title?.trim() || page.title;
717
+ const description = page.meta_description?.trim() || page.excerpt?.trim() || plainTextFromMarkdown(page.body, 160) || page.title;
718
+ const languages = {};
719
+ for (const alt of alternates) {
720
+ languages[alt.locale] = absoluteJmpSiteUrl(alt.path, siteUrl);
721
+ }
722
+ const canonical = absoluteJmpSiteUrl(page.path, siteUrl);
723
+ return {
724
+ title,
725
+ description,
726
+ alternates: {
727
+ canonical,
728
+ languages: Object.keys(languages).length > 0 ? languages : void 0
729
+ },
730
+ openGraph: {
731
+ title,
732
+ description,
733
+ type: "website",
734
+ url: canonical
735
+ },
736
+ twitter: {
737
+ card: "summary",
738
+ title,
739
+ description
740
+ }
741
+ };
742
+ }
743
+ function buildJmpLandingIndexMetadata(kind, siteUrl) {
744
+ const hub = JMP_LANDING_HUB[kind];
745
+ return {
746
+ title: hub.title,
747
+ description: hub.description,
748
+ alternates: {
749
+ canonical: absoluteJmpSiteUrl(hub.path, siteUrl)
750
+ }
751
+ };
752
+ }
753
+ function DefaultJsonLd({
754
+ data,
755
+ id
756
+ }) {
757
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
758
+ "script",
759
+ {
760
+ type: "application/ld+json",
761
+ dangerouslySetInnerHTML: { __html: JSON.stringify(item) }
762
+ },
763
+ `${id}-${index}`
764
+ )) });
765
+ }
766
+ function DefaultBreadcrumbs({
767
+ items,
768
+ LinkComponent
769
+ }) {
770
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { style: { display: "flex", flexWrap: "wrap", gap: "0.5rem", listStyle: "none", padding: 0 }, children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
771
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: " / " }) : null,
772
+ index < items.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkComponent, { href: item.path, children: item.name }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.name })
773
+ ] }, item.path)) }) });
774
+ }
775
+ function resolveComponents(components) {
776
+ const LinkComponent = components?.Link ?? import_link.default;
777
+ return {
778
+ Link: LinkComponent,
779
+ renderBody: components?.renderBody ?? ((body) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { style: { whiteSpace: "pre-wrap", fontFamily: "inherit" }, children: body }) })),
780
+ renderBreadcrumbs: components?.renderBreadcrumbs ?? ((items) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultBreadcrumbs, { items, LinkComponent })),
781
+ renderJsonLd: components?.renderJsonLd ?? ((data, id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultJsonLd, { data, id }))
782
+ };
783
+ }
784
+ async function JmpLandingDetailPage({
785
+ kind,
786
+ slug,
787
+ locale,
788
+ siteUrl,
789
+ components,
790
+ className,
791
+ classNames
792
+ }) {
793
+ const detail = await loadLandingPageDetail(kind, slug, locale);
794
+ if (!detail) {
795
+ (0, import_navigation.notFound)();
796
+ }
797
+ const ui = resolveComponents(components);
798
+ const resolvedSiteUrl = resolveJmpSiteUrl(siteUrl) ?? void 0;
799
+ const { page, schema_json_ld } = detail;
800
+ const hub = JMP_LANDING_HUB[kind];
801
+ const displayTitle = page.headline?.trim() || page.title;
802
+ const breadcrumbItems = [
803
+ { name: "Home", path: "/" },
804
+ { name: hub.breadcrumb, path: hub.path },
805
+ { name: page.title, path: page.path }
806
+ ];
807
+ const related = filterJmpLandingPagesForDefaultLocale(
808
+ await loadLandingPages({ kind }),
809
+ kind
810
+ ).filter((item) => item.id !== page.id && item.locale === page.locale);
811
+ const jsonLdBlocks = [
812
+ ...schema_json_ld ?? [],
813
+ jmpBreadcrumbListJsonLd(breadcrumbItems, resolvedSiteUrl)
814
+ ];
815
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
816
+ ui.renderJsonLd(jsonLdBlocks, `jmp-landing-${page.id}-ld-json`),
817
+ ui.renderBreadcrumbs(breadcrumbItems),
818
+ page.excerpt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.excerpt, children: page.excerpt }) : null,
819
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: displayTitle }),
820
+ page.body ? ui.renderBody(page.body) : null,
821
+ related.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
822
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { className: classNames?.relatedTitle, children: [
823
+ "More ",
824
+ hub.breadcrumb.toLowerCase()
825
+ ] }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: classNames?.list, children: related.slice(0, 8).map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: classNames?.listItem, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.Link, { href: item.path, children: item.title }) }, item.id)) })
827
+ ] }) : null
828
+ ] });
829
+ }
830
+ async function JmpLandingIndexPage({
831
+ kind,
832
+ components,
833
+ className,
834
+ classNames
835
+ }) {
836
+ const ui = resolveComponents(components);
837
+ const hub = JMP_LANDING_HUB[kind];
838
+ const pages = filterJmpLandingPagesForDefaultLocale(
839
+ await loadLandingPages({ kind }),
840
+ kind
841
+ );
842
+ const breadcrumbItems = [
843
+ { name: "Home", path: "/" },
844
+ { name: hub.breadcrumb, path: hub.path }
845
+ ];
846
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
847
+ ui.renderBreadcrumbs(breadcrumbItems),
848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: hub.title }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.hubDescription, children: hub.description }),
850
+ pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: classNames?.list, children: pages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: classNames?.listItem, children: [
851
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.Link, { href: page.path, children: page.title }),
852
+ page.excerpt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.excerpt, children: page.excerpt }) : null
853
+ ] }, page.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.emptyState, children: "Pages will appear here once published in the JMP dashboard." })
854
+ ] });
855
+ }
856
+ // Annotate the CommonJS export names for ESM import in node:
857
+ 0 && (module.exports = {
858
+ JMP_LANDING_HUB,
859
+ JmpLandingDetailPage,
860
+ JmpLandingIndexPage,
861
+ absoluteJmpSiteUrl,
862
+ buildJmpLandingIndexMetadata,
863
+ buildJmpLandingMetadata,
864
+ buildJmpLandingSitemapRoutes,
865
+ filterJmpLandingPagesForDefaultLocale,
866
+ filterJmpLandingPagesForLocalizedRoutes,
867
+ generateJmpLandingStaticParams,
868
+ generateJmpLocalizedLandingStaticParams,
869
+ jmpBreadcrumbListJsonLd,
870
+ jmpLandingKindSegment,
871
+ jmpLocalizedLandingStaticParams,
872
+ plainTextFromMarkdown,
873
+ resolveJmpSiteUrl
874
+ });
875
+ //# sourceMappingURL=landing-pages.cjs.map