@cavuno/board 1.40.0 → 1.42.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.
Files changed (48) hide show
  1. package/dist/{_spec-DRbgPIlN.d.mts → _spec-LdkTxfx0.d.mts} +55 -0
  2. package/dist/{_spec-DRbgPIlN.d.ts → _spec-LdkTxfx0.d.ts} +55 -0
  3. package/dist/{board-DCiNpgFf.d.mts → board-Cs93Lf23.d.mts} +1 -1
  4. package/dist/{board-BxPUtrOl.d.ts → board-FxC9Wdhi.d.ts} +1 -1
  5. package/dist/filters.d.mts +2 -2
  6. package/dist/filters.d.ts +2 -2
  7. package/dist/format.d.mts +3 -3
  8. package/dist/format.d.ts +3 -3
  9. package/dist/go.d.mts +65 -0
  10. package/dist/go.d.ts +65 -0
  11. package/dist/go.js +130 -0
  12. package/dist/go.mjs +107 -0
  13. package/dist/index.d.mts +44 -10
  14. package/dist/index.d.ts +44 -10
  15. package/dist/index.js +42 -1
  16. package/dist/index.mjs +42 -1
  17. package/dist/{jobs-DhePKSRe.d.mts → jobs-BtazrWfv.d.mts} +1 -1
  18. package/dist/{jobs-DAGAVHQL.d.ts → jobs-TIAPH3Ol.d.ts} +1 -1
  19. package/dist/paths.d.mts +25 -1
  20. package/dist/paths.d.ts +25 -1
  21. package/dist/paths.js +20 -1
  22. package/dist/paths.mjs +20 -1
  23. package/dist/roles-vY0AXhoc.d.mts +82 -0
  24. package/dist/roles-vY0AXhoc.d.ts +82 -0
  25. package/dist/route-contract.d.mts +257 -0
  26. package/dist/route-contract.d.ts +257 -0
  27. package/dist/route-contract.js +1131 -0
  28. package/dist/route-contract.mjs +1108 -0
  29. package/dist/{salaries-cqb78kg0.d.ts → salaries-BEAqkaFt.d.ts} +2 -2
  30. package/dist/{salaries-D6SUVMmt.d.mts → salaries-CB9Ve4kS.d.mts} +2 -2
  31. package/dist/{search-BalPAS0P.d.ts → search-DWmHHXn8.d.ts} +1 -1
  32. package/dist/{search-DYUQzCq_.d.mts → search-YhoVpXFY.d.mts} +1 -1
  33. package/dist/seo.d.mts +4 -4
  34. package/dist/seo.d.ts +4 -4
  35. package/dist/server.d.mts +5 -5
  36. package/dist/server.d.ts +5 -5
  37. package/dist/sitemap.d.mts +5 -5
  38. package/dist/sitemap.d.ts +5 -5
  39. package/dist/sitemap.js +8 -1
  40. package/dist/sitemap.mjs +8 -1
  41. package/dist/suggest.d.mts +2 -2
  42. package/dist/suggest.d.ts +2 -2
  43. package/dist/well-known.d.mts +113 -0
  44. package/dist/well-known.d.ts +113 -0
  45. package/dist/well-known.js +561 -0
  46. package/dist/well-known.mjs +538 -0
  47. package/package.json +31 -1
  48. package/skills/manifest.json +1 -1
@@ -0,0 +1,1131 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/route-contract/index.ts
21
+ var route_contract_exports = {};
22
+ __export(route_contract_exports, {
23
+ ALL_ROLES: () => ALL_ROLES,
24
+ CANONICAL_MANIFEST: () => CANONICAL_MANIFEST,
25
+ REQUIRED_ROLES: () => REQUIRED_ROLES,
26
+ ROLE_INDEX_PATHS: () => ROLE_INDEX_PATHS,
27
+ ROLE_PARAM_REGISTRY: () => ROLE_PARAM_REGISTRY,
28
+ checkRoleTemplate: () => checkRoleTemplate,
29
+ classifyRoutes: () => classifyRoutes,
30
+ compileManifest: () => compileManifest,
31
+ compileManifestFromTree: () => compileManifestFromTree,
32
+ createBoardLinkResolver: () => createBoardLinkResolver,
33
+ deriveRoutes: () => deriveRoutes,
34
+ detectFramework: () => detectFramework,
35
+ enumerateRouteEntries: () => enumerateRouteEntries,
36
+ extractCavunoPageMarker: () => extractCavunoPageMarker,
37
+ extractParamNames: () => extractParamNames,
38
+ getReactRouterCandidatePaths: () => getReactRouterCandidatePaths,
39
+ isRouteRole: () => isRouteRole,
40
+ matchPathAgainstHistory: () => matchPathAgainstHistory,
41
+ matchPathToTemplate: () => matchPathToTemplate,
42
+ parseAstroRoutes: () => parseAstroRoutes,
43
+ parseNextRoutes: () => parseNextRoutes,
44
+ parseNuxtRoutes: () => parseNuxtRoutes,
45
+ parseReactRouterRoutes: () => parseReactRouterRoutes,
46
+ parseSvelteKitRoutes: () => parseSvelteKitRoutes,
47
+ parseTanStackRoutes: () => parseTanStackRoutes,
48
+ sameParamSet: () => sameParamSet,
49
+ validateManifest: () => validateManifest
50
+ });
51
+ module.exports = __toCommonJS(route_contract_exports);
52
+
53
+ // src/paths/index.ts
54
+ function jobDetailPath(companySlug, jobSlug) {
55
+ return `/companies/${companySlug}/jobs/${jobSlug}`;
56
+ }
57
+ function jobsCategoryPath(categorySlug) {
58
+ return `/jobs/${categorySlug}`;
59
+ }
60
+ function jobsSkillPath(skillSlug) {
61
+ return `/jobs/skills/${skillSlug}`;
62
+ }
63
+ function jobsLocationPath(placeSlug) {
64
+ return `/jobs/locations/${placeSlug}`;
65
+ }
66
+ function companyPath(companySlug) {
67
+ return `/companies/${companySlug}`;
68
+ }
69
+ function companyMarketPath(marketSlug) {
70
+ return `/companies/markets/${marketSlug}`;
71
+ }
72
+ function companySalaryPath(companySlug) {
73
+ return `/companies/${companySlug}/salaries`;
74
+ }
75
+ function salaryTitlePath(titleSlug) {
76
+ return `/salaries/titles/${titleSlug}`;
77
+ }
78
+ function salarySkillPath(skillSlug) {
79
+ return `/salaries/skills/${skillSlug}`;
80
+ }
81
+ function salaryLocationPath(placeSlug) {
82
+ return `/salaries/locations/${placeSlug}`;
83
+ }
84
+ function blogPostPath(postSlug) {
85
+ return `/blog/${postSlug}`;
86
+ }
87
+ function blogTagPath(tagSlug) {
88
+ return `/blog/tag/${tagSlug}`;
89
+ }
90
+ function blogAuthorPath(authorSlug) {
91
+ return `/blog/author/${authorSlug}`;
92
+ }
93
+ var BOARD_PATHS = {
94
+ home: "/",
95
+ jobs: "/jobs",
96
+ companies: "/companies",
97
+ salaries: "/salaries",
98
+ salaryCompanies: "/salaries/companies",
99
+ salaryTitles: "/salaries/titles",
100
+ salarySkills: "/salaries/skills",
101
+ salaryLocations: "/salaries/locations",
102
+ blog: "/blog",
103
+ about: "/about",
104
+ privacyPolicy: "/privacy-policy",
105
+ termsOfService: "/terms-of-service",
106
+ cookiePolicy: "/cookie-policy",
107
+ impressum: "/impressum",
108
+ talent: "/talent",
109
+ employers: "/employers",
110
+ /**
111
+ * Alert-email surfaces (ADR-0090 D1/D4). Promoted out of raw literals in
112
+ * the job-alerts composer so /go indirection and email composition share
113
+ * one definition.
114
+ */
115
+ alertsManage: "/alerts/manage",
116
+ alertsConfirm: "/alerts/confirm"
117
+ };
118
+
119
+ // src/route-contract/roles.ts
120
+ var ROLE_PARAM_REGISTRY = {
121
+ jobDetail: ["companySlug", "jobSlug"],
122
+ jobsCategory: ["categorySlug"],
123
+ jobsSkill: ["skillSlug"],
124
+ jobsLocation: ["placeSlug"],
125
+ company: ["companySlug"],
126
+ companyMarket: ["marketSlug"],
127
+ companySalary: ["companySlug"],
128
+ salaryTitle: ["titleSlug"],
129
+ salarySkill: ["skillSlug"],
130
+ salaryLocation: ["placeSlug"],
131
+ blogPost: ["postSlug"],
132
+ blogTag: ["tagSlug"],
133
+ blogAuthor: ["authorSlug"],
134
+ home: [],
135
+ jobs: [],
136
+ companies: [],
137
+ salaries: [],
138
+ salaryCompanies: [],
139
+ salaryTitles: [],
140
+ salarySkills: [],
141
+ salaryLocations: [],
142
+ blog: [],
143
+ about: [],
144
+ privacyPolicy: [],
145
+ termsOfService: [],
146
+ cookiePolicy: [],
147
+ impressum: [],
148
+ talent: [],
149
+ employers: [],
150
+ alertsManage: [],
151
+ alertsConfirm: []
152
+ };
153
+ var REQUIRED_ROLES = [
154
+ "jobDetail",
155
+ "alertsManage",
156
+ "alertsConfirm"
157
+ ];
158
+ var ALL_ROLES = Object.keys(
159
+ ROLE_PARAM_REGISTRY
160
+ );
161
+ var ROLE_SET = new Set(ALL_ROLES);
162
+ function isRouteRole(value) {
163
+ return ROLE_SET.has(value);
164
+ }
165
+ var CANONICAL_MANIFEST = {
166
+ version: 1,
167
+ roles: {
168
+ jobDetail: jobDetailPath(":companySlug", ":jobSlug"),
169
+ jobsCategory: jobsCategoryPath(":categorySlug"),
170
+ jobsSkill: jobsSkillPath(":skillSlug"),
171
+ jobsLocation: jobsLocationPath(":placeSlug"),
172
+ company: companyPath(":companySlug"),
173
+ companyMarket: companyMarketPath(":marketSlug"),
174
+ companySalary: companySalaryPath(":companySlug"),
175
+ salaryTitle: salaryTitlePath(":titleSlug"),
176
+ salarySkill: salarySkillPath(":skillSlug"),
177
+ salaryLocation: salaryLocationPath(":placeSlug"),
178
+ blogPost: blogPostPath(":postSlug"),
179
+ blogTag: blogTagPath(":tagSlug"),
180
+ blogAuthor: blogAuthorPath(":authorSlug"),
181
+ home: BOARD_PATHS.home,
182
+ jobs: BOARD_PATHS.jobs,
183
+ companies: BOARD_PATHS.companies,
184
+ salaries: BOARD_PATHS.salaries,
185
+ salaryCompanies: BOARD_PATHS.salaryCompanies,
186
+ salaryTitles: BOARD_PATHS.salaryTitles,
187
+ salarySkills: BOARD_PATHS.salarySkills,
188
+ salaryLocations: BOARD_PATHS.salaryLocations,
189
+ blog: BOARD_PATHS.blog,
190
+ about: BOARD_PATHS.about,
191
+ privacyPolicy: BOARD_PATHS.privacyPolicy,
192
+ termsOfService: BOARD_PATHS.termsOfService,
193
+ cookiePolicy: BOARD_PATHS.cookiePolicy,
194
+ impressum: BOARD_PATHS.impressum,
195
+ talent: BOARD_PATHS.talent,
196
+ employers: BOARD_PATHS.employers,
197
+ alertsManage: "/alerts/manage",
198
+ alertsConfirm: "/alerts/confirm"
199
+ }
200
+ };
201
+
202
+ // src/route-contract/params.ts
203
+ function extractParamNames(template) {
204
+ const names = [];
205
+ const re = /:([A-Za-z_][A-Za-z0-9_]*)/g;
206
+ let match;
207
+ while ((match = re.exec(template)) !== null) {
208
+ const name = match[1];
209
+ if (name !== void 0) names.push(name);
210
+ }
211
+ return names;
212
+ }
213
+ function sameParamSet(a, b) {
214
+ if (a.length !== b.length) return false;
215
+ const sortedA = [...a].sort();
216
+ const sortedB = [...b].sort();
217
+ for (let i = 0; i < sortedA.length; i++) {
218
+ if (sortedA[i] !== sortedB[i]) return false;
219
+ }
220
+ return true;
221
+ }
222
+ function paramSetKey(params) {
223
+ return [...params].sort().join("\0");
224
+ }
225
+
226
+ // src/route-contract/classify.ts
227
+ var SHARED_SIGNATURE_COUNTS = (() => {
228
+ const counts = /* @__PURE__ */ new Map();
229
+ for (const role of ALL_ROLES) {
230
+ const params = ROLE_PARAM_REGISTRY[role];
231
+ if (params.length === 0) continue;
232
+ const key = paramSetKey(params);
233
+ counts.set(key, (counts.get(key) ?? 0) + 1);
234
+ }
235
+ return counts;
236
+ })();
237
+ function isSignatureCandidate(route, role) {
238
+ const registry = ROLE_PARAM_REGISTRY[role];
239
+ const routeParams = extractParamNames(route.template);
240
+ if (registry.length === 0) {
241
+ return route.template === CANONICAL_MANIFEST.roles[role];
242
+ }
243
+ if (!sameParamSet(routeParams, registry)) return false;
244
+ const shareCount = SHARED_SIGNATURE_COUNTS.get(paramSetKey(registry)) ?? 0;
245
+ if (shareCount > 1) {
246
+ return route.template === CANONICAL_MANIFEST.roles[role];
247
+ }
248
+ return true;
249
+ }
250
+ function classifyRoutes(routes, markers) {
251
+ const assignments = {};
252
+ const ambiguities = [];
253
+ const claimed = /* @__PURE__ */ new Set();
254
+ if (markers && markers.size > 0) {
255
+ const claimsByRole = /* @__PURE__ */ new Map();
256
+ for (const route of routes) {
257
+ if (route.sourcePath === void 0) continue;
258
+ const role = markers.get(route.sourcePath);
259
+ if (role === void 0) continue;
260
+ const list = claimsByRole.get(role);
261
+ if (list) list.push(route);
262
+ else claimsByRole.set(role, [route]);
263
+ }
264
+ for (const [role, candidates] of claimsByRole) {
265
+ for (const c of candidates) claimed.add(c);
266
+ if (candidates.length === 1) {
267
+ assignments[role] = candidates[0];
268
+ } else {
269
+ ambiguities.push({ role, candidates: [...candidates] });
270
+ }
271
+ }
272
+ }
273
+ const pool = routes.filter((r) => !claimed.has(r));
274
+ for (const role of ALL_ROLES) {
275
+ if (assignments[role] !== void 0) continue;
276
+ if (ambiguities.some((a) => a.role === role)) continue;
277
+ const candidates = pool.filter((r) => isSignatureCandidate(r, role));
278
+ if (candidates.length === 0) continue;
279
+ if (candidates.length === 1) {
280
+ const only = candidates[0];
281
+ if (claimed.has(only)) {
282
+ continue;
283
+ }
284
+ assignments[role] = only;
285
+ claimed.add(only);
286
+ continue;
287
+ }
288
+ ambiguities.push({ role, candidates: [...candidates] });
289
+ }
290
+ const missingRequired = REQUIRED_ROLES.filter(
291
+ (role) => assignments[role] === void 0
292
+ );
293
+ return { assignments, ambiguities, missingRequired };
294
+ }
295
+
296
+ // src/route-contract/validate.ts
297
+ var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:-]+$/;
298
+ function checkRoleTemplate(role, template) {
299
+ const errors = [];
300
+ if (typeof template !== "string" || template.length === 0) {
301
+ errors.push({
302
+ code: "invalid_template",
303
+ message: `role '${role}' template must be a non-empty string`,
304
+ role
305
+ });
306
+ return errors;
307
+ }
308
+ if (!template.startsWith("/")) {
309
+ errors.push({
310
+ code: "invalid_template",
311
+ message: `role '${role}' template must start with '/', got ${JSON.stringify(template)}`,
312
+ role
313
+ });
314
+ return errors;
315
+ }
316
+ if (/\s/.test(template)) {
317
+ errors.push({
318
+ code: "invalid_template",
319
+ message: `role '${role}' template must not contain whitespace, got ${JSON.stringify(template)}`,
320
+ role
321
+ });
322
+ return errors;
323
+ }
324
+ if (template.includes("?") || template.includes("#")) {
325
+ errors.push({
326
+ code: "template_query_or_fragment",
327
+ message: `role '${role}' template must not contain '?' or '#', got ${JSON.stringify(template)}`,
328
+ role
329
+ });
330
+ return errors;
331
+ }
332
+ if (template.startsWith("//") || template.includes("//")) {
333
+ errors.push({
334
+ code: "template_double_slash",
335
+ message: `role '${role}' template must not contain '//', got ${JSON.stringify(template)}`,
336
+ role
337
+ });
338
+ return errors;
339
+ }
340
+ for (const seg of template.split("/")) {
341
+ if (seg === "." || seg === "..") {
342
+ errors.push({
343
+ code: "template_dot_segment",
344
+ message: `role '${role}' template must not contain '.' or '..' segments, got ${JSON.stringify(template)}`,
345
+ role
346
+ });
347
+ return errors;
348
+ }
349
+ }
350
+ if (!TEMPLATE_CHARSET_RE.test(template)) {
351
+ errors.push({
352
+ code: "template_illegal_charset",
353
+ message: `role '${role}' template contains illegal characters, got ${JSON.stringify(template)}`,
354
+ role
355
+ });
356
+ return errors;
357
+ }
358
+ const registry = ROLE_PARAM_REGISTRY[role];
359
+ const tokens = extractParamNames(template);
360
+ if (registry.length === 0) {
361
+ if (tokens.length > 0) {
362
+ errors.push({
363
+ code: "static_has_params",
364
+ message: `static role '${role}' template must not contain ':' tokens, got ${template}`,
365
+ role,
366
+ token: tokens[0]
367
+ });
368
+ }
369
+ } else {
370
+ const registrySet = new Set(registry);
371
+ for (const token of tokens) {
372
+ if (!registrySet.has(token)) {
373
+ errors.push({
374
+ code: "unregistered_param",
375
+ message: `role '${role}' template references unregistered param ':${token}'`,
376
+ role,
377
+ token
378
+ });
379
+ }
380
+ }
381
+ const retained = tokens.filter((t) => registrySet.has(t));
382
+ if (retained.length === 0) {
383
+ errors.push({
384
+ code: "missing_entity_param",
385
+ message: `role '${role}' template must contain at least one registry param (${registry.join(", ")})`,
386
+ role
387
+ });
388
+ }
389
+ }
390
+ return errors;
391
+ }
392
+ function validateManifest(value) {
393
+ const errors = [];
394
+ if (value === null || typeof value !== "object") {
395
+ return {
396
+ ok: false,
397
+ errors: [
398
+ {
399
+ code: "invalid_shape",
400
+ message: "manifest must be an object"
401
+ }
402
+ ]
403
+ };
404
+ }
405
+ const record = value;
406
+ if (record.version !== 1) {
407
+ errors.push({
408
+ code: "invalid_version",
409
+ message: `manifest version must be 1, got ${JSON.stringify(record.version)}`
410
+ });
411
+ }
412
+ if (record.roles === null || typeof record.roles !== "object" || Array.isArray(record.roles)) {
413
+ errors.push({
414
+ code: "invalid_shape",
415
+ message: "manifest.roles must be an object"
416
+ });
417
+ return { ok: false, errors };
418
+ }
419
+ const rolesIn = record.roles;
420
+ const rolesOut = {};
421
+ for (const [key, template] of Object.entries(rolesIn)) {
422
+ if (!isRouteRole(key)) {
423
+ errors.push({
424
+ code: "unknown_role",
425
+ message: `unknown role '${key}'`,
426
+ role: key
427
+ });
428
+ continue;
429
+ }
430
+ if (typeof template !== "string") {
431
+ errors.push({
432
+ code: "invalid_template",
433
+ message: `role '${key}' template must be a non-empty string`,
434
+ role: key
435
+ });
436
+ continue;
437
+ }
438
+ const role = key;
439
+ const roleErrors = checkRoleTemplate(role, template);
440
+ if (roleErrors.length > 0) {
441
+ errors.push(...roleErrors);
442
+ continue;
443
+ }
444
+ rolesOut[role] = template;
445
+ }
446
+ if (errors.length > 0) {
447
+ return { ok: false, errors };
448
+ }
449
+ return {
450
+ ok: true,
451
+ manifest: { version: 1, roles: rolesOut }
452
+ };
453
+ }
454
+
455
+ // src/route-contract/compile.ts
456
+ var REQUIRED_SET = new Set(REQUIRED_ROLES);
457
+ function compileManifest(routes, markers) {
458
+ const { assignments, ambiguities, missingRequired } = classifyRoutes(
459
+ routes,
460
+ markers
461
+ );
462
+ const roles = {};
463
+ const warnings = [];
464
+ const blocking = new Set(missingRequired);
465
+ for (const role of ALL_ROLES) {
466
+ const entry = assignments[role];
467
+ if (entry === void 0) continue;
468
+ const templateErrors = checkRoleTemplate(role, entry.template);
469
+ if (templateErrors.length > 0) {
470
+ const exact = templateErrors.map((e) => e.message).join("; ");
471
+ warnings.push(
472
+ `role '${role}' template rejected: ${exact} \u2014 add export const cavunoPage = '${role}' on the correct page or fix the template`
473
+ );
474
+ if (REQUIRED_SET.has(role)) {
475
+ blocking.add(role);
476
+ }
477
+ continue;
478
+ }
479
+ roles[role] = entry.template;
480
+ }
481
+ for (const amb of ambiguities) {
482
+ const kind = REQUIRED_SET.has(amb.role) ? "required" : "optional";
483
+ warnings.push(
484
+ `${kind} role '${amb.role}' is ambiguous \u2014 add export const cavunoPage = '${amb.role}' to disambiguate`
485
+ );
486
+ }
487
+ for (const role of ALL_ROLES) {
488
+ if (REQUIRED_SET.has(role)) continue;
489
+ if (roles[role] !== void 0) continue;
490
+ if (assignments[role] !== void 0) continue;
491
+ if (ambiguities.some((a) => a.role === role)) continue;
492
+ warnings.push(`optional role '${role}' is unassigned`);
493
+ }
494
+ return {
495
+ manifest: { version: 1, roles },
496
+ blockingMissing: REQUIRED_ROLES.filter((r) => blocking.has(r)),
497
+ warnings,
498
+ ambiguities
499
+ };
500
+ }
501
+
502
+ // src/route-contract/enumerate/collect.ts
503
+ function collectRoutes(routes) {
504
+ const byPath = /* @__PURE__ */ new Map();
505
+ for (const derived of routes) {
506
+ if (derived === null) continue;
507
+ const existing = byPath.get(derived.path);
508
+ if (existing === void 0) {
509
+ byPath.set(derived.path, {
510
+ navigable: derived.navigable,
511
+ sourcePath: derived.sourcePath
512
+ });
513
+ } else {
514
+ byPath.set(derived.path, {
515
+ navigable: existing.navigable || derived.navigable,
516
+ sourcePath: existing.sourcePath ?? derived.sourcePath
517
+ });
518
+ }
519
+ }
520
+ return [...byPath.entries()].map(([path, meta]) => {
521
+ const route = { path, navigable: meta.navigable };
522
+ if (meta.sourcePath !== void 0) {
523
+ route.sourcePath = meta.sourcePath;
524
+ }
525
+ return route;
526
+ }).sort((a, b) => {
527
+ if (a.path === "/") return -1;
528
+ if (b.path === "/") return 1;
529
+ return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
530
+ });
531
+ }
532
+ function normalizeListingPath(filePath) {
533
+ return filePath.replace(/^\.\//, "");
534
+ }
535
+
536
+ // src/route-contract/enumerate/astro.ts
537
+ var PAGE_EXT = /\.(astro|md|mdx|html)$/i;
538
+ function parseAstroRoutes(paths) {
539
+ const normalized = paths.map(normalizeListingPath);
540
+ const preferSrc = normalized.some((p) => p.startsWith("src/pages/"));
541
+ return collectRoutes(
542
+ normalized.map((p) => pathToRoute(p, preferSrc ? "src/pages/" : null))
543
+ );
544
+ }
545
+ function pathToRoute(normalized, forcedRoot) {
546
+ const root = pagesRoot(normalized, forcedRoot);
547
+ if (root === null) return null;
548
+ if (!PAGE_EXT.test(normalized)) return null;
549
+ const afterRoot = normalized.slice(root.length);
550
+ if (afterRoot === "api" || afterRoot.startsWith("api/")) return null;
551
+ const rel = afterRoot.replace(PAGE_EXT, "");
552
+ if (rel.length === 0) return null;
553
+ const segments = rel.split("/").filter((s) => s.length > 0);
554
+ const urlSegments = [];
555
+ let hasDynamic = false;
556
+ for (const seg of segments) {
557
+ if (seg.startsWith("_")) return null;
558
+ if (seg === "index") continue;
559
+ if (seg.startsWith("[") && seg.endsWith("]")) {
560
+ hasDynamic = true;
561
+ let inner = seg.slice(1, -1);
562
+ if (inner.startsWith("[") && inner.endsWith("]")) {
563
+ inner = inner.slice(1, -1);
564
+ }
565
+ if (inner.startsWith("...")) {
566
+ urlSegments.push("*");
567
+ } else {
568
+ urlSegments.push(`:${inner}`);
569
+ }
570
+ continue;
571
+ }
572
+ urlSegments.push(seg);
573
+ }
574
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
575
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
576
+ }
577
+ function pagesRoot(normalized, forcedRoot) {
578
+ if (forcedRoot !== null) {
579
+ return normalized.startsWith(forcedRoot) ? forcedRoot : null;
580
+ }
581
+ if (normalized.startsWith("src/pages/")) return "src/pages/";
582
+ if (normalized.startsWith("pages/")) return "pages/";
583
+ return null;
584
+ }
585
+
586
+ // src/route-contract/enumerate/react-router.ts
587
+ function getReactRouterCandidatePaths(paths) {
588
+ const candidates = [];
589
+ for (const raw of paths) {
590
+ const p = normalizeListingPath(raw);
591
+ const base = p.split("/").pop() ?? p;
592
+ const lower = base.toLowerCase();
593
+ if (/^app\.(tsx|ts|jsx|js)$/i.test(base)) {
594
+ candidates.push(p);
595
+ continue;
596
+ }
597
+ if (/^router\.(tsx|ts|jsx|js)$/i.test(base)) {
598
+ candidates.push(p);
599
+ continue;
600
+ }
601
+ if (lower.includes("routes") && /\.(tsx|ts|jsx|js)$/i.test(base) && !base.endsWith(".d.ts")) {
602
+ candidates.push(p);
603
+ }
604
+ }
605
+ return candidates.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
606
+ }
607
+ function parseReactRouterRoutes(contents) {
608
+ const found = [];
609
+ for (const source of contents) {
610
+ const jsxRe = /\bpath\s*=\s*(?:["']([^"']+)["']|\{\s*["']([^"']+)["']\s*\})/g;
611
+ let m;
612
+ while ((m = jsxRe.exec(source)) !== null) {
613
+ const raw = m[1] ?? m[2];
614
+ if (raw === void 0) continue;
615
+ const route = toDerived(raw);
616
+ if (route) found.push(route);
617
+ }
618
+ const objRe = /\bpath\s*:\s*["']([^"']+)["']/g;
619
+ while ((m = objRe.exec(source)) !== null) {
620
+ const raw = m[1];
621
+ if (raw === void 0) continue;
622
+ const route = toDerived(raw);
623
+ if (route) found.push(route);
624
+ }
625
+ }
626
+ return collectRoutes(found);
627
+ }
628
+ function toDerived(rawPath) {
629
+ const trimmed = rawPath.trim();
630
+ if (trimmed.length === 0) return null;
631
+ if (trimmed === "*" || trimmed === "/*") return null;
632
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
633
+ const path = withSlash.length > 1 && withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
634
+ const hasDynamic = path.includes(":") || path.includes("*") || path.includes("(");
635
+ return { path, navigable: !hasDynamic };
636
+ }
637
+
638
+ // src/route-contract/enumerate/detect.ts
639
+ function detectFramework(paths) {
640
+ const normalized = paths.map(normalizeListingPath);
641
+ if (hasExplicitTanStackMarkers(normalized)) return "tanstack";
642
+ if (isNext(normalized)) return "next";
643
+ if (isAstro(normalized)) return "astro";
644
+ if (isSvelteKit(normalized)) return "sveltekit";
645
+ if (isNuxt(normalized)) return "nuxt";
646
+ if (hasMarkerlessTanStackRoutes(normalized)) return "tanstack";
647
+ if (getReactRouterCandidatePaths(normalized).length > 0) {
648
+ return "react-router";
649
+ }
650
+ return null;
651
+ }
652
+ function hasExplicitTanStackMarkers(paths) {
653
+ return paths.some(
654
+ (p) => p === "src/routeTree.gen.ts" || p === "src/routeTree.gen.js" || /^src\/routes\/__root\.[^/]+$/i.test(p)
655
+ );
656
+ }
657
+ function hasMarkerlessTanStackRoutes(paths) {
658
+ return paths.some((p) => {
659
+ if (!/^src\/routes\/.+\.(tsx|jsx)$/i.test(p)) return false;
660
+ if (p.endsWith(".d.ts")) return false;
661
+ const base = p.split("/").pop() ?? "";
662
+ if (base.startsWith("+")) return false;
663
+ return true;
664
+ });
665
+ }
666
+ function isNext(paths) {
667
+ const hasConfig = paths.some((p) => /^next\.config\.[^/]+$/i.test(p));
668
+ if (!hasConfig) return false;
669
+ return paths.some(
670
+ (p) => (
671
+ // App router page files
672
+ /^(src\/)?app\/.*\/page\.(tsx|ts|jsx|js)$/i.test(p) || /^(src\/)?app\/page\.(tsx|ts|jsx|js)$/i.test(p) || // Pages router (exclude special _app/_document/_error and api)
673
+ /^(src\/)?pages\/.+\.(tsx|ts|jsx|js)$/i.test(p) && !/\/api\//i.test(p) && !/(?:^|\/)_(?:app|document|error)\./i.test(p)
674
+ )
675
+ );
676
+ }
677
+ function isAstro(paths) {
678
+ return paths.some((p) => /^astro\.config\.[^/]+$/i.test(p));
679
+ }
680
+ function isSvelteKit(paths) {
681
+ const hasConfig = paths.some((p) => /^svelte\.config\.[^/]+$/i.test(p));
682
+ if (!hasConfig) return false;
683
+ return paths.some((p) => /(?:^|\/)\+page(?:@[^/]*)?\.[^/]+$/i.test(p));
684
+ }
685
+ function isNuxt(paths) {
686
+ return paths.some((p) => /^nuxt\.config\.[^/]+$/i.test(p));
687
+ }
688
+
689
+ // src/route-contract/enumerate/next.ts
690
+ var APP_PAGE_FILE = /(?:^|\/)page\.(tsx|ts|jsx|js)$/i;
691
+ var PAGES_EXT = /\.(tsx|ts|jsx|js)$/i;
692
+ var PAGES_SPECIAL = /(?:^|\/)_(?:app|document|error)\.[^/]+$/i;
693
+ function parseNextRoutes(paths) {
694
+ return collectRoutes([
695
+ ...paths.map(pathToAppRoute),
696
+ ...paths.map(pathToPagesRoute)
697
+ ]);
698
+ }
699
+ function pathToAppRoute(filePath) {
700
+ const normalized = normalizeListingPath(filePath);
701
+ const root = appRoot(normalized);
702
+ if (root === null) return null;
703
+ if (!APP_PAGE_FILE.test(normalized)) return null;
704
+ const afterRoot = normalized.slice(root.length);
705
+ const rel = afterRoot.replace(/(?:^|\/)page\.(tsx|ts|jsx|js)$/i, "");
706
+ const segments = rel.length === 0 ? [] : rel.split("/");
707
+ const urlSegments = [];
708
+ let hasDynamic = false;
709
+ for (const seg of segments) {
710
+ if (seg.length === 0) continue;
711
+ if (seg.startsWith("_")) return null;
712
+ if (seg.startsWith("@")) continue;
713
+ if (isInterceptingSegment(seg)) return null;
714
+ if (seg.startsWith("(") && seg.endsWith(")")) continue;
715
+ if (seg.startsWith("[") && seg.endsWith("]")) {
716
+ hasDynamic = true;
717
+ let inner = seg.slice(1, -1);
718
+ if (inner.startsWith("[") && inner.endsWith("]")) {
719
+ inner = inner.slice(1, -1);
720
+ }
721
+ if (inner.startsWith("...")) {
722
+ urlSegments.push("*");
723
+ } else {
724
+ urlSegments.push(`:${inner}`);
725
+ }
726
+ continue;
727
+ }
728
+ urlSegments.push(seg);
729
+ }
730
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
731
+ if (path === "/api" || path.startsWith("/api/")) return null;
732
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
733
+ }
734
+ function isInterceptingSegment(seg) {
735
+ return /^\(\.{1,}\)(\(\.{1,}\))*/.test(seg);
736
+ }
737
+ function appRoot(normalized) {
738
+ if (normalized.startsWith("src/app/")) return "src/app/";
739
+ if (normalized.startsWith("app/")) return "app/";
740
+ return null;
741
+ }
742
+ function pathToPagesRoute(filePath) {
743
+ const normalized = normalizeListingPath(filePath);
744
+ const root = pagesRoot2(normalized);
745
+ if (root === null) return null;
746
+ if (!PAGES_EXT.test(normalized)) return null;
747
+ if (normalized.endsWith(".d.ts")) return null;
748
+ const afterRoot = normalized.slice(root.length);
749
+ if (afterRoot === "api" || afterRoot.startsWith("api/")) return null;
750
+ if (PAGES_SPECIAL.test(afterRoot)) return null;
751
+ const rel = afterRoot.replace(PAGES_EXT, "");
752
+ if (rel.length === 0) return null;
753
+ const segments = rel.split("/").filter((s) => s.length > 0);
754
+ const urlSegments = [];
755
+ let hasDynamic = false;
756
+ for (const seg of segments) {
757
+ if (seg.startsWith("_")) return null;
758
+ if (seg === "index") continue;
759
+ if (seg.startsWith("[") && seg.endsWith("]")) {
760
+ hasDynamic = true;
761
+ let inner = seg.slice(1, -1);
762
+ if (inner.startsWith("[") && inner.endsWith("]")) {
763
+ inner = inner.slice(1, -1);
764
+ }
765
+ if (inner.startsWith("...")) {
766
+ urlSegments.push("*");
767
+ } else {
768
+ urlSegments.push(`:${inner}`);
769
+ }
770
+ continue;
771
+ }
772
+ urlSegments.push(seg);
773
+ }
774
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
775
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
776
+ }
777
+ function pagesRoot2(normalized) {
778
+ if (normalized.startsWith("src/pages/")) return "src/pages/";
779
+ if (normalized.startsWith("pages/")) return "pages/";
780
+ return null;
781
+ }
782
+
783
+ // src/route-contract/enumerate/nuxt.ts
784
+ var PAGE_EXT2 = /\.(vue|tsx|ts|jsx|js)$/i;
785
+ function parseNuxtRoutes(paths) {
786
+ return collectRoutes(paths.map(pathToRoute2));
787
+ }
788
+ function pathToRoute2(filePath) {
789
+ const normalized = normalizeListingPath(filePath);
790
+ const root = pagesRoot3(normalized);
791
+ if (root === null) return null;
792
+ if (!PAGE_EXT2.test(normalized)) return null;
793
+ if (normalized.endsWith(".d.ts")) return null;
794
+ const afterRoot = normalized.slice(root.length);
795
+ const rel = afterRoot.replace(PAGE_EXT2, "");
796
+ if (rel.length === 0) return null;
797
+ const segments = rel.split("/").filter((s) => s.length > 0);
798
+ const urlSegments = [];
799
+ let hasDynamic = false;
800
+ for (const seg of segments) {
801
+ if (seg.startsWith("_")) return null;
802
+ if (seg === "index") continue;
803
+ if (seg.startsWith("[") && seg.endsWith("]")) {
804
+ hasDynamic = true;
805
+ let inner = seg.slice(1, -1);
806
+ if (inner.startsWith("[") && inner.endsWith("]")) {
807
+ inner = inner.slice(1, -1);
808
+ }
809
+ if (inner.startsWith("...")) {
810
+ urlSegments.push("*");
811
+ } else {
812
+ urlSegments.push(`:${inner}`);
813
+ }
814
+ continue;
815
+ }
816
+ urlSegments.push(seg);
817
+ }
818
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
819
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
820
+ }
821
+ function pagesRoot3(normalized) {
822
+ if (normalized.startsWith("src/pages/")) return "src/pages/";
823
+ if (normalized.startsWith("pages/")) return "pages/";
824
+ return null;
825
+ }
826
+
827
+ // src/route-contract/enumerate/sveltekit.ts
828
+ var ROUTES_PREFIX = "src/routes/";
829
+ var ROOT_PAGE = /^\+page(?:@[^/]*)?\.svelte$/i;
830
+ var NESTED_PAGE = /\/\+page(?:@[^/]*)?\.svelte$/i;
831
+ function parseSvelteKitRoutes(paths) {
832
+ return collectRoutes(paths.map(pathToRoute3));
833
+ }
834
+ function pathToRoute3(filePath) {
835
+ const normalized = normalizeListingPath(filePath);
836
+ if (!normalized.startsWith(ROUTES_PREFIX)) return null;
837
+ const afterRoot = normalized.slice(ROUTES_PREFIX.length);
838
+ let rel;
839
+ if (ROOT_PAGE.test(afterRoot)) {
840
+ rel = "";
841
+ } else if (NESTED_PAGE.test(afterRoot)) {
842
+ rel = afterRoot.replace(NESTED_PAGE, "");
843
+ } else {
844
+ return null;
845
+ }
846
+ const segments = rel.length === 0 ? [] : rel.split("/");
847
+ const urlSegments = [];
848
+ let hasDynamic = false;
849
+ for (const seg of segments) {
850
+ if (seg.length === 0) continue;
851
+ if (seg.startsWith("(") && seg.endsWith(")")) continue;
852
+ if (seg.startsWith("_")) return null;
853
+ if (seg.startsWith("[") && seg.endsWith("]")) {
854
+ hasDynamic = true;
855
+ let inner = seg.slice(1, -1);
856
+ if (inner.startsWith("[") && inner.endsWith("]")) {
857
+ inner = inner.slice(1, -1);
858
+ }
859
+ if (inner.startsWith("...")) {
860
+ urlSegments.push("*");
861
+ } else {
862
+ urlSegments.push(`:${inner}`);
863
+ }
864
+ continue;
865
+ }
866
+ urlSegments.push(seg);
867
+ }
868
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
869
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
870
+ }
871
+
872
+ // src/route-contract/enumerate/tanstack.ts
873
+ var ROUTES_PREFIX2 = "src/routes/";
874
+ var PAGE_EXT3 = /\.(tsx|jsx)$/i;
875
+ function parseTanStackRoutes(paths) {
876
+ return collectRoutes(paths.map(pathToRoute4));
877
+ }
878
+ function pathToRoute4(filePath) {
879
+ const normalized = normalizeListingPath(filePath);
880
+ if (!normalized.startsWith(ROUTES_PREFIX2)) return null;
881
+ if (normalized.endsWith(".d.ts")) return null;
882
+ if (!PAGE_EXT3.test(normalized)) return null;
883
+ const rel = normalized.slice(ROUTES_PREFIX2.length).replace(PAGE_EXT3, "");
884
+ if (rel.length === 0) return null;
885
+ if (rel === "route" || rel.endsWith("/route") || rel.endsWith(".route")) {
886
+ return null;
887
+ }
888
+ if (rel.includes("[") || rel.includes("{")) return null;
889
+ const rawSegments = rel.split("/").flatMap((part) => part.split(".")).filter((seg) => seg.length > 0);
890
+ const urlSegments = [];
891
+ let hasDynamic = false;
892
+ let hadIndex = false;
893
+ let hadConcrete = false;
894
+ for (const seg of rawSegments) {
895
+ if (seg.startsWith("-")) return null;
896
+ if (seg === "__root") return null;
897
+ if (seg.startsWith("_")) continue;
898
+ if (seg === "index") {
899
+ hadIndex = true;
900
+ continue;
901
+ }
902
+ if (seg.startsWith("$")) {
903
+ hasDynamic = true;
904
+ hadConcrete = true;
905
+ const param = seg.slice(1);
906
+ urlSegments.push(param.length > 0 ? `:${param}` : "*");
907
+ continue;
908
+ }
909
+ hadConcrete = true;
910
+ urlSegments.push(seg);
911
+ }
912
+ if (urlSegments.length === 0 && !hadIndex && !hadConcrete) {
913
+ return null;
914
+ }
915
+ const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
916
+ return { path, navigable: !hasDynamic, sourcePath: normalized };
917
+ }
918
+
919
+ // src/route-contract/enumerate/index.ts
920
+ function enumerateDerived(paths) {
921
+ const framework = detectFramework(paths);
922
+ if (framework === null) return [];
923
+ switch (framework) {
924
+ case "tanstack":
925
+ return parseTanStackRoutes(paths);
926
+ case "next":
927
+ return parseNextRoutes(paths);
928
+ case "astro":
929
+ return parseAstroRoutes(paths);
930
+ case "sveltekit":
931
+ return parseSvelteKitRoutes(paths);
932
+ case "nuxt":
933
+ return parseNuxtRoutes(paths);
934
+ case "react-router":
935
+ return [];
936
+ default: {
937
+ const _exhaustive = framework;
938
+ return _exhaustive;
939
+ }
940
+ }
941
+ }
942
+ function deriveRoutes(paths) {
943
+ return enumerateDerived(paths).map(({ path, navigable }) => ({
944
+ path,
945
+ navigable
946
+ }));
947
+ }
948
+ function enumerateRouteEntries(paths) {
949
+ return enumerateDerived(paths).map(({ path, sourcePath }) => {
950
+ const entry = { template: path };
951
+ if (sourcePath !== void 0) entry.sourcePath = sourcePath;
952
+ return entry;
953
+ });
954
+ }
955
+
956
+ // src/route-contract/markers.ts
957
+ function kebabToCamel(token) {
958
+ return token.replace(/-([a-zA-Z0-9])/g, (_, ch) => ch.toUpperCase());
959
+ }
960
+ function extractCavunoPageMarker(contents) {
961
+ const re = /export\s+const\s+cavunoPage\s*=\s*(['"])([A-Za-z_][A-Za-z0-9_-]*)\1\s*(?:as\s+const)?\s*;?/;
962
+ const match = re.exec(contents);
963
+ if (!match) return null;
964
+ const raw = match[2];
965
+ if (raw === void 0) return null;
966
+ if (isRouteRole(raw)) return raw;
967
+ const camel = kebabToCamel(raw);
968
+ if (isRouteRole(camel)) return camel;
969
+ return null;
970
+ }
971
+
972
+ // src/route-contract/compile-from-tree.ts
973
+ function compileManifestFromTree(files) {
974
+ const paths = files.map((f) => f.path);
975
+ const routes = enumerateRouteEntries(paths);
976
+ const markers = /* @__PURE__ */ new Map();
977
+ for (const file of files) {
978
+ const role = extractCavunoPageMarker(file.contents);
979
+ if (role !== null) {
980
+ markers.set(file.path.replace(/^\.\//, ""), role);
981
+ }
982
+ }
983
+ return compileManifest(routes, markers.size > 0 ? markers : void 0);
984
+ }
985
+
986
+ // src/route-contract/resolve-links.ts
987
+ var LAYER_ORDER = [
988
+ "declared",
989
+ "wellKnown",
990
+ "inferred"
991
+ ];
992
+ var REQUIRED_SET2 = new Set(REQUIRED_ROLES);
993
+ function trySubstitute(template, role, params) {
994
+ const registry = ROLE_PARAM_REGISTRY[role];
995
+ const registrySet = new Set(registry);
996
+ const tokens = extractParamNames(template);
997
+ for (const name of tokens) {
998
+ if (!registrySet.has(name)) {
999
+ return null;
1000
+ }
1001
+ if (params?.[name] === void 0) {
1002
+ return null;
1003
+ }
1004
+ }
1005
+ let path = template;
1006
+ for (const name of tokens) {
1007
+ path = path.split(`:${name}`).join(params[name]);
1008
+ }
1009
+ return path;
1010
+ }
1011
+ function forceSubstitute(template, params) {
1012
+ return template.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, name) => {
1013
+ return params?.[name] ?? "";
1014
+ });
1015
+ }
1016
+ function templateFor(manifest, role) {
1017
+ const t = manifest.roles[role];
1018
+ if (typeof t !== "string" || t.length === 0) return void 0;
1019
+ return t;
1020
+ }
1021
+ function createBoardLinkResolver(layers) {
1022
+ const bySource = /* @__PURE__ */ new Map();
1023
+ for (const layer of layers) {
1024
+ if (!bySource.has(layer.source)) {
1025
+ bySource.set(layer.source, layer);
1026
+ }
1027
+ }
1028
+ return {
1029
+ pathFor(role, params) {
1030
+ let sawMissingParam = false;
1031
+ for (const source of LAYER_ORDER) {
1032
+ if (source === "inferred" && REQUIRED_SET2.has(role)) {
1033
+ continue;
1034
+ }
1035
+ const layer = bySource.get(source);
1036
+ if (!layer) continue;
1037
+ const template = templateFor(layer.manifest, role);
1038
+ if (template === void 0) continue;
1039
+ const path2 = trySubstitute(template, role, params);
1040
+ if (path2 !== null) {
1041
+ return { path: path2, source };
1042
+ }
1043
+ sawMissingParam = true;
1044
+ }
1045
+ const canonicalTemplate = templateFor(CANONICAL_MANIFEST, role) ?? CANONICAL_MANIFEST.roles[role] ?? "/";
1046
+ const path = trySubstitute(canonicalTemplate, role, params);
1047
+ if (path !== null) {
1048
+ return sawMissingParam ? { path, source: "canonical", fallback: "missing-param" } : { path, source: "canonical" };
1049
+ }
1050
+ return {
1051
+ path: forceSubstitute(canonicalTemplate, params),
1052
+ source: "canonical",
1053
+ fallback: "missing-param"
1054
+ };
1055
+ }
1056
+ };
1057
+ }
1058
+
1059
+ // src/route-contract/match-history.ts
1060
+ var ROLE_INDEX_PATHS = {
1061
+ jobDetail: BOARD_PATHS.jobs,
1062
+ jobsCategory: BOARD_PATHS.jobs,
1063
+ jobsSkill: BOARD_PATHS.jobs,
1064
+ jobsLocation: BOARD_PATHS.jobs,
1065
+ company: BOARD_PATHS.companies,
1066
+ companyMarket: BOARD_PATHS.companies,
1067
+ companySalary: BOARD_PATHS.companies,
1068
+ salaryTitle: BOARD_PATHS.salaries,
1069
+ salarySkill: BOARD_PATHS.salaries,
1070
+ salaryLocation: BOARD_PATHS.salaries,
1071
+ salaryCompanies: BOARD_PATHS.salaries,
1072
+ salaryTitles: BOARD_PATHS.salaries,
1073
+ salarySkills: BOARD_PATHS.salaries,
1074
+ salaryLocations: BOARD_PATHS.salaries,
1075
+ salaries: BOARD_PATHS.salaries,
1076
+ blogPost: BOARD_PATHS.blog,
1077
+ blogTag: BOARD_PATHS.blog,
1078
+ blogAuthor: BOARD_PATHS.blog,
1079
+ blog: BOARD_PATHS.blog,
1080
+ home: BOARD_PATHS.home,
1081
+ jobs: BOARD_PATHS.jobs,
1082
+ companies: BOARD_PATHS.companies,
1083
+ about: BOARD_PATHS.about,
1084
+ privacyPolicy: BOARD_PATHS.privacyPolicy,
1085
+ termsOfService: BOARD_PATHS.termsOfService,
1086
+ cookiePolicy: BOARD_PATHS.cookiePolicy,
1087
+ impressum: BOARD_PATHS.impressum,
1088
+ talent: BOARD_PATHS.talent,
1089
+ employers: BOARD_PATHS.employers,
1090
+ alertsManage: BOARD_PATHS.home,
1091
+ alertsConfirm: BOARD_PATHS.home
1092
+ };
1093
+ function splitSegments(path) {
1094
+ const withoutLeading = path.startsWith("/") ? path.slice(1) : path;
1095
+ const withoutTrailing = withoutLeading.length > 1 && withoutLeading.endsWith("/") ? withoutLeading.slice(0, -1) : withoutLeading;
1096
+ if (withoutTrailing === "") return [];
1097
+ return withoutTrailing.split("/");
1098
+ }
1099
+ function matchPathToTemplate(template, pathname) {
1100
+ const templateSegs = splitSegments(template);
1101
+ const pathSegs = splitSegments(pathname);
1102
+ if (templateSegs.length !== pathSegs.length) {
1103
+ return null;
1104
+ }
1105
+ const params = {};
1106
+ for (let i = 0; i < templateSegs.length; i++) {
1107
+ const tSeg = templateSegs[i];
1108
+ const pSeg = pathSegs[i];
1109
+ if (tSeg.startsWith(":") && tSeg.length > 1) {
1110
+ if (pSeg.length === 0) return null;
1111
+ if (pSeg.includes("/") || pSeg.includes("\\")) return null;
1112
+ params[tSeg.slice(1)] = pSeg;
1113
+ continue;
1114
+ }
1115
+ if (tSeg !== pSeg) return null;
1116
+ }
1117
+ return params;
1118
+ }
1119
+ function matchPathAgainstHistory(entries, pathname) {
1120
+ for (const entry of entries) {
1121
+ const params = matchPathToTemplate(entry.template, pathname);
1122
+ if (params !== null) {
1123
+ return {
1124
+ role: entry.role,
1125
+ template: entry.template,
1126
+ params
1127
+ };
1128
+ }
1129
+ }
1130
+ return null;
1131
+ }