@cavuno/board 4.42.0 → 4.45.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 (49) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/{_spec-B2aizgxr.d.mts → _spec-CdNZDaaR.d.mts} +39 -0
  3. package/dist/{_spec-B2aizgxr.d.ts → _spec-CdNZDaaR.d.ts} +39 -0
  4. package/dist/bin.mjs +570 -10
  5. package/dist/{blog-BkACbXY0.d.ts → blog-ZvLJ6w1Q.d.ts} +1 -1
  6. package/dist/{blog-CYHSnNTZ.d.mts → blog-uV51b4Cg.d.mts} +1 -1
  7. package/dist/{board-DWeT9Jbl.d.ts → board-3JU14eF3.d.ts} +2 -2
  8. package/dist/{board-DK4HmSty.d.mts → board-CJw-MJpf.d.mts} +2 -2
  9. package/dist/{breadcrumbs-nTfIWzRh.d.mts → breadcrumbs-BY1UMCbL.d.mts} +2 -2
  10. package/dist/{breadcrumbs-CtKa2zPe.d.ts → breadcrumbs-CXvdv6Ry.d.ts} +2 -2
  11. package/dist/browser/cavuno-board.global.min.js +1 -1
  12. package/dist/{chunk-WSO6JN5C.mjs → chunk-DZD3DAEY.mjs} +2 -0
  13. package/dist/doctor.d.mts +1 -0
  14. package/dist/doctor.d.ts +1 -0
  15. package/dist/doctor.js +570 -10
  16. package/dist/doctor.mjs +570 -10
  17. package/dist/filters.d.mts +2 -2
  18. package/dist/filters.d.ts +2 -2
  19. package/dist/format.d.mts +4 -4
  20. package/dist/format.d.ts +4 -4
  21. package/dist/index.d.mts +107 -14
  22. package/dist/index.d.ts +107 -14
  23. package/dist/index.js +129 -8
  24. package/dist/index.mjs +128 -9
  25. package/dist/{jobs-SAkNWgAB.d.ts → jobs-C7UvN_dW.d.ts} +1 -1
  26. package/dist/{jobs-C_eO-WfM.d.mts → jobs-D9ERWWda.d.mts} +1 -1
  27. package/dist/{search-Cb3Aoa4c.d.mts → search-BMSNK85o.d.mts} +1 -1
  28. package/dist/{search-CVcMFOuJ.d.ts → search-D0TFnn1x.d.ts} +1 -1
  29. package/dist/{seo-DPVud7SH.d.mts → seo-C3WmgifR.d.mts} +1 -1
  30. package/dist/{seo-DgFBQTIK.d.ts → seo-CYmVKHsu.d.ts} +1 -1
  31. package/dist/seo-files.d.mts +25 -3
  32. package/dist/seo-files.d.ts +25 -3
  33. package/dist/seo-files.js +44 -10
  34. package/dist/seo-files.mjs +44 -10
  35. package/dist/seo.d.mts +6 -6
  36. package/dist/seo.d.ts +6 -6
  37. package/dist/server.d.mts +6 -6
  38. package/dist/server.d.ts +6 -6
  39. package/dist/server.mjs +1 -1
  40. package/dist/sitemap.d.mts +6 -6
  41. package/dist/sitemap.d.ts +6 -6
  42. package/dist/sitemap.mjs +1 -1
  43. package/dist/suggest.d.mts +2 -2
  44. package/dist/suggest.d.ts +2 -2
  45. package/package.json +1 -1
  46. package/skills/cavuno-board-filters/SKILL.md +13 -0
  47. package/skills/cavuno-board-i18n/SKILL.md +10 -0
  48. package/skills/cavuno-board-post-a-job/SKILL.md +2 -1
  49. package/skills/manifest.json +1 -1
package/dist/index.js CHANGED
@@ -209,6 +209,8 @@ var BOARD_API_ERROR_CODES = [
209
209
  "talent_list_not_found",
210
210
  "sourced_candidate_not_found",
211
211
  "sourced_convert_unavailable",
212
+ // Saved people on a talent list: a list holds at most 500 people.
213
+ "talent_list_full",
212
214
  // Candidate job-access paywall
213
215
  "candidate_paywall_access_required",
214
216
  "paywall_already_active",
@@ -403,7 +405,7 @@ async function clearSession(storage) {
403
405
  }
404
406
 
405
407
  // src/version.ts
406
- var SDK_VERSION = "4.42.0";
408
+ var SDK_VERSION = "4.45.0";
407
409
 
408
410
  // src/client.ts
409
411
  function isRawBody(body) {
@@ -1346,7 +1348,11 @@ function jobPostingNamespace(client) {
1346
1348
  { ...options, method: "POST", body: input }
1347
1349
  );
1348
1350
  },
1349
- /** List the credit options for a verified email (an option → `selectedBilling`). */
1351
+ /**
1352
+ * List the credit options for a verified email. To spend one, pass the
1353
+ * option as `selectedBilling` and the same `verificationToken` as
1354
+ * `billingVerificationToken` to `create`.
1355
+ */
1350
1356
  getBillingOptions(input, options) {
1351
1357
  return client.fetch(
1352
1358
  "/job-postings/billing-options",
@@ -2486,9 +2492,12 @@ function meNamespace(client) {
2486
2492
  }
2487
2493
  },
2488
2494
  /**
2489
- * Company-owned saved predicates over the talent directory.
2490
- * Approved members share the same lists. Optional `job` binds a list
2491
- * to one company job; pass `job: null` on update to unbind.
2495
+ * Company-owned saved searches over the talent directory. Each holds
2496
+ * named filters and, optionally, saved people (`members`). Approved
2497
+ * members share the same lists. Optional `job` links a list to one
2498
+ * company job; pass `job: null` on update to unlink. Linking a list
2499
+ * shortlists everyone saved to it onto that job; unlinking changes no
2500
+ * pipeline.
2492
2501
  */
2493
2502
  talentLists: {
2494
2503
  /**
@@ -2530,7 +2539,10 @@ function meNamespace(client) {
2530
2539
  { ...options, method: "PATCH", body }
2531
2540
  );
2532
2541
  },
2533
- /** Delete a talent list. Sourced membership is unaffected. */
2542
+ /**
2543
+ * Delete a talent list and the people saved to it. Job pipelines and
2544
+ * sourced membership are unaffected.
2545
+ */
2534
2546
  remove(slug, listId, options) {
2535
2547
  return client.fetch(
2536
2548
  `/me/companies/${encodeURIComponent(
@@ -2538,6 +2550,90 @@ function meNamespace(client) {
2538
2550
  )}/talent-lists/${encodeURIComponent(listId)}`,
2539
2551
  { ...options, method: "DELETE" }
2540
2552
  );
2553
+ },
2554
+ /**
2555
+ * The talent lists of a company one candidate is saved to — for
2556
+ * showing which lists a Save control has ticked.
2557
+ *
2558
+ * @example
2559
+ * const { data } = await board.me.companies.talentLists.memberships(
2560
+ * 'acme',
2561
+ * { candidate: candidateId },
2562
+ * );
2563
+ * const saved = new Set(data.map((m) => m.listId));
2564
+ */
2565
+ memberships(slug, query, options) {
2566
+ return client.fetch(
2567
+ `/me/companies/${encodeURIComponent(slug)}/talent-list-memberships`,
2568
+ { ...options, query }
2569
+ );
2570
+ },
2571
+ /**
2572
+ * People saved to a talent list (at most 500 per list).
2573
+ */
2574
+ members: {
2575
+ /**
2576
+ * List the people saved to a list as directory cards, most
2577
+ * recently saved first unless `sort` or a keyword (`q`) is set;
2578
+ * with a keyword, best match first. Cards are redacted as in the
2579
+ * public directory when the board sells profile unlocks, except
2580
+ * for candidates the company has unlocked. Runs the public
2581
+ * talent directory search, with the same filters, restricted to
2582
+ * the saved people, so a "Saved" view can combine with the
2583
+ * list's filters. Blocked, hidden and deactivated
2584
+ * candidates are omitted. `total` counts every visible saved
2585
+ * person; `count` counts the ones the filters keep.
2586
+ *
2587
+ * @example
2588
+ * const { data, total } =
2589
+ * await board.me.companies.talentLists.members.list(
2590
+ * 'acme',
2591
+ * listId,
2592
+ * { skill: 'react' },
2593
+ * );
2594
+ */
2595
+ list(slug, listId, query, options) {
2596
+ return client.fetch(
2597
+ `/me/companies/${encodeURIComponent(
2598
+ slug
2599
+ )}/talent-lists/${encodeURIComponent(listId)}/members`,
2600
+ { ...options, query }
2601
+ );
2602
+ },
2603
+ /**
2604
+ * Save a candidate to a list. Idempotent: saving someone already
2605
+ * on it returns `created: false`. When the list is linked to a
2606
+ * job, the candidate is also shortlisted onto that job unless they
2607
+ * already have a pipeline row there. A full list raises
2608
+ * `talent_list_full` (409).
2609
+ *
2610
+ * @example
2611
+ * await board.me.companies.talentLists.members.add('acme', listId, {
2612
+ * candidateBoardUserId: candidateId,
2613
+ * });
2614
+ */
2615
+ add(slug, listId, body, options) {
2616
+ return client.fetch(
2617
+ `/me/companies/${encodeURIComponent(
2618
+ slug
2619
+ )}/talent-lists/${encodeURIComponent(listId)}/members`,
2620
+ { ...options, method: "POST", body }
2621
+ );
2622
+ },
2623
+ /**
2624
+ * Remove a candidate from a list. Idempotent. Never changes a job
2625
+ * pipeline, including the linked job's.
2626
+ */
2627
+ remove(slug, listId, candidateBoardUserId, options) {
2628
+ return client.fetch(
2629
+ `/me/companies/${encodeURIComponent(
2630
+ slug
2631
+ )}/talent-lists/${encodeURIComponent(
2632
+ listId
2633
+ )}/members/${encodeURIComponent(candidateBoardUserId)}`,
2634
+ { ...options, method: "DELETE" }
2635
+ );
2636
+ }
2541
2637
  }
2542
2638
  },
2543
2639
  /**
@@ -3753,9 +3849,10 @@ function sitemapNamespace(client) {
3753
3849
  function talentNamespace(client) {
3754
3850
  return {
3755
3851
  /**
3756
- * List the board's public talent directory — candidate cards ranked by
3852
+ * List the board's talent directory — candidate cards ranked by
3757
3853
  * job-search status then most-recently-updated, the same ordering the
3758
- * hosted `/talent` page renders. Only `public` profiles appear.
3854
+ * hosted `/talent` page renders. Anonymous callers see `public` profiles;
3855
+ * a signed-in board user also sees `logged_in_only` ones.
3759
3856
  *
3760
3857
  * Throws `talent_directory_restricted` (403) when the board restricts the
3761
3858
  * directory to employers (render an employer upsell), or
@@ -3788,6 +3885,30 @@ function talentNamespace(client) {
3788
3885
  `/talent/${encodeURIComponent(handle)}`,
3789
3886
  options
3790
3887
  );
3888
+ },
3889
+ places: {
3890
+ /**
3891
+ * List the places the board's directory candidates live in, busiest
3892
+ * first, each with how many candidates the caller can see there (a
3893
+ * country counts every candidate in its cities). Anonymous callers get
3894
+ * counts of `public` profiles; a signed-in board user also counts
3895
+ * `logged_in_only` ones. Pass a place's `slug` as `talent.list`'s
3896
+ * `place` filter. With `q`, returns location search matches instead,
3897
+ * ranked as `taxonomy.places.list` ranks them.
3898
+ *
3899
+ * Same access errors as `talent.list`. Returns an empty list until the
3900
+ * board's place counts are built.
3901
+ *
3902
+ * @example
3903
+ * const { data: places } = await board.talent.places.list({ limit: 50 });
3904
+ * const { data: matches } = await board.talent.places.list({ q: 'lon' });
3905
+ */
3906
+ list(query, options) {
3907
+ return client.fetch("/talent/places", {
3908
+ ...options,
3909
+ query
3910
+ });
3911
+ }
3791
3912
  }
3792
3913
  };
3793
3914
  }
package/dist/index.mjs CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  isSsoRequired,
26
26
  isUnauthorized,
27
27
  isValidationError
28
- } from "./chunk-WSO6JN5C.mjs";
28
+ } from "./chunk-DZD3DAEY.mjs";
29
29
 
30
30
  // src/query.ts
31
31
  function toSearchParams(query) {
@@ -46,7 +46,7 @@ function toSearchParams(query) {
46
46
  }
47
47
 
48
48
  // src/version.ts
49
- var SDK_VERSION = "4.42.0";
49
+ var SDK_VERSION = "4.45.0";
50
50
 
51
51
  // src/client.ts
52
52
  function isRawBody(body) {
@@ -989,7 +989,11 @@ function jobPostingNamespace(client) {
989
989
  { ...options, method: "POST", body: input }
990
990
  );
991
991
  },
992
- /** List the credit options for a verified email (an option → `selectedBilling`). */
992
+ /**
993
+ * List the credit options for a verified email. To spend one, pass the
994
+ * option as `selectedBilling` and the same `verificationToken` as
995
+ * `billingVerificationToken` to `create`.
996
+ */
993
997
  getBillingOptions(input, options) {
994
998
  return client.fetch(
995
999
  "/job-postings/billing-options",
@@ -2129,9 +2133,12 @@ function meNamespace(client) {
2129
2133
  }
2130
2134
  },
2131
2135
  /**
2132
- * Company-owned saved predicates over the talent directory.
2133
- * Approved members share the same lists. Optional `job` binds a list
2134
- * to one company job; pass `job: null` on update to unbind.
2136
+ * Company-owned saved searches over the talent directory. Each holds
2137
+ * named filters and, optionally, saved people (`members`). Approved
2138
+ * members share the same lists. Optional `job` links a list to one
2139
+ * company job; pass `job: null` on update to unlink. Linking a list
2140
+ * shortlists everyone saved to it onto that job; unlinking changes no
2141
+ * pipeline.
2135
2142
  */
2136
2143
  talentLists: {
2137
2144
  /**
@@ -2173,7 +2180,10 @@ function meNamespace(client) {
2173
2180
  { ...options, method: "PATCH", body }
2174
2181
  );
2175
2182
  },
2176
- /** Delete a talent list. Sourced membership is unaffected. */
2183
+ /**
2184
+ * Delete a talent list and the people saved to it. Job pipelines and
2185
+ * sourced membership are unaffected.
2186
+ */
2177
2187
  remove(slug, listId, options) {
2178
2188
  return client.fetch(
2179
2189
  `/me/companies/${encodeURIComponent(
@@ -2181,6 +2191,90 @@ function meNamespace(client) {
2181
2191
  )}/talent-lists/${encodeURIComponent(listId)}`,
2182
2192
  { ...options, method: "DELETE" }
2183
2193
  );
2194
+ },
2195
+ /**
2196
+ * The talent lists of a company one candidate is saved to — for
2197
+ * showing which lists a Save control has ticked.
2198
+ *
2199
+ * @example
2200
+ * const { data } = await board.me.companies.talentLists.memberships(
2201
+ * 'acme',
2202
+ * { candidate: candidateId },
2203
+ * );
2204
+ * const saved = new Set(data.map((m) => m.listId));
2205
+ */
2206
+ memberships(slug, query, options) {
2207
+ return client.fetch(
2208
+ `/me/companies/${encodeURIComponent(slug)}/talent-list-memberships`,
2209
+ { ...options, query }
2210
+ );
2211
+ },
2212
+ /**
2213
+ * People saved to a talent list (at most 500 per list).
2214
+ */
2215
+ members: {
2216
+ /**
2217
+ * List the people saved to a list as directory cards, most
2218
+ * recently saved first unless `sort` or a keyword (`q`) is set;
2219
+ * with a keyword, best match first. Cards are redacted as in the
2220
+ * public directory when the board sells profile unlocks, except
2221
+ * for candidates the company has unlocked. Runs the public
2222
+ * talent directory search, with the same filters, restricted to
2223
+ * the saved people, so a "Saved" view can combine with the
2224
+ * list's filters. Blocked, hidden and deactivated
2225
+ * candidates are omitted. `total` counts every visible saved
2226
+ * person; `count` counts the ones the filters keep.
2227
+ *
2228
+ * @example
2229
+ * const { data, total } =
2230
+ * await board.me.companies.talentLists.members.list(
2231
+ * 'acme',
2232
+ * listId,
2233
+ * { skill: 'react' },
2234
+ * );
2235
+ */
2236
+ list(slug, listId, query, options) {
2237
+ return client.fetch(
2238
+ `/me/companies/${encodeURIComponent(
2239
+ slug
2240
+ )}/talent-lists/${encodeURIComponent(listId)}/members`,
2241
+ { ...options, query }
2242
+ );
2243
+ },
2244
+ /**
2245
+ * Save a candidate to a list. Idempotent: saving someone already
2246
+ * on it returns `created: false`. When the list is linked to a
2247
+ * job, the candidate is also shortlisted onto that job unless they
2248
+ * already have a pipeline row there. A full list raises
2249
+ * `talent_list_full` (409).
2250
+ *
2251
+ * @example
2252
+ * await board.me.companies.talentLists.members.add('acme', listId, {
2253
+ * candidateBoardUserId: candidateId,
2254
+ * });
2255
+ */
2256
+ add(slug, listId, body, options) {
2257
+ return client.fetch(
2258
+ `/me/companies/${encodeURIComponent(
2259
+ slug
2260
+ )}/talent-lists/${encodeURIComponent(listId)}/members`,
2261
+ { ...options, method: "POST", body }
2262
+ );
2263
+ },
2264
+ /**
2265
+ * Remove a candidate from a list. Idempotent. Never changes a job
2266
+ * pipeline, including the linked job's.
2267
+ */
2268
+ remove(slug, listId, candidateBoardUserId, options) {
2269
+ return client.fetch(
2270
+ `/me/companies/${encodeURIComponent(
2271
+ slug
2272
+ )}/talent-lists/${encodeURIComponent(
2273
+ listId
2274
+ )}/members/${encodeURIComponent(candidateBoardUserId)}`,
2275
+ { ...options, method: "DELETE" }
2276
+ );
2277
+ }
2184
2278
  }
2185
2279
  },
2186
2280
  /**
@@ -3396,9 +3490,10 @@ function sitemapNamespace(client) {
3396
3490
  function talentNamespace(client) {
3397
3491
  return {
3398
3492
  /**
3399
- * List the board's public talent directory — candidate cards ranked by
3493
+ * List the board's talent directory — candidate cards ranked by
3400
3494
  * job-search status then most-recently-updated, the same ordering the
3401
- * hosted `/talent` page renders. Only `public` profiles appear.
3495
+ * hosted `/talent` page renders. Anonymous callers see `public` profiles;
3496
+ * a signed-in board user also sees `logged_in_only` ones.
3402
3497
  *
3403
3498
  * Throws `talent_directory_restricted` (403) when the board restricts the
3404
3499
  * directory to employers (render an employer upsell), or
@@ -3431,6 +3526,30 @@ function talentNamespace(client) {
3431
3526
  `/talent/${encodeURIComponent(handle)}`,
3432
3527
  options
3433
3528
  );
3529
+ },
3530
+ places: {
3531
+ /**
3532
+ * List the places the board's directory candidates live in, busiest
3533
+ * first, each with how many candidates the caller can see there (a
3534
+ * country counts every candidate in its cities). Anonymous callers get
3535
+ * counts of `public` profiles; a signed-in board user also counts
3536
+ * `logged_in_only` ones. Pass a place's `slug` as `talent.list`'s
3537
+ * `place` filter. With `q`, returns location search matches instead,
3538
+ * ranked as `taxonomy.places.list` ranks them.
3539
+ *
3540
+ * Same access errors as `talent.list`. Returns an empty list until the
3541
+ * board's place counts are built.
3542
+ *
3543
+ * @example
3544
+ * const { data: places } = await board.talent.places.list({ limit: 50 });
3545
+ * const { data: matches } = await board.talent.places.list({ q: 'lon' });
3546
+ */
3547
+ list(query, options) {
3548
+ return client.fetch("/talent/places", {
3549
+ ...options,
3550
+ query
3551
+ });
3552
+ }
3434
3553
  }
3435
3554
  };
3436
3555
  }
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.js';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.js';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.mjs';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.mjs';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.mjs';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.mjs';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.js';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.js';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.mjs';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.mjs';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B2aizgxr.js';
1
+ import { S as Schemas } from './_spec-CdNZDaaR.js';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-DPVud7SH.mjs';
2
- import './_spec-B2aizgxr.mjs';
1
+ import { B as BoardSeo } from './seo-C3WmgifR.mjs';
2
+ import './_spec-CdNZDaaR.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
@@ -18,13 +18,35 @@ declare const INDEXNOW_CACHE_CONTROL = "public, max-age=86400, must-revalidate";
18
18
  type SeoFileResponseInit = {
19
19
  method?: string;
20
20
  };
21
+ /**
22
+ * The board's own listing roots, for boards that rename or localize their
23
+ * URL words (for example `/vacatures` in place of `/jobs`). Each root scopes
24
+ * one group of pagination `Disallow` rules. A missing, root (`/`) or unsafe
25
+ * value keeps the default English root for that group.
26
+ */
27
+ type RobotsListingPaths = {
28
+ jobs?: string | null;
29
+ companies?: string | null;
30
+ talent?: string | null;
31
+ };
21
32
  /** Lines of the hosted robots body (no sitemap line). Exported so tests and hosted can share the literal. */
22
33
  declare function robotsBody(input: {
23
34
  canonicalBase: string;
24
35
  sandbox?: boolean;
36
+ listingPaths?: RobotsListingPaths | null;
37
+ /**
38
+ * Listing roots of the board's secondary languages, served under a
39
+ * language prefix with that language's own URL words (for example
40
+ * `/de/stellen`). Each safe root adds one group of pagination rules after
41
+ * the base ones, in order. A missing, root or unsafe value adds nothing,
42
+ * and a root already listed is not repeated.
43
+ */
44
+ localeListingPaths?: readonly RobotsListingPaths[] | null;
25
45
  }): string;
26
46
  declare function robotsResponse(seo: Pick<BoardSeo, 'canonicalBase'> & {
27
47
  sandbox?: boolean;
48
+ listingPaths?: RobotsListingPaths | null;
49
+ localeListingPaths?: readonly RobotsListingPaths[] | null;
28
50
  }, init?: SeoFileResponseInit): Response;
29
51
  declare function adsTxtResponse(seo: Pick<BoardSeo, 'adsTxt'>, init?: SeoFileResponseInit): Response;
30
52
  declare function indexNowResponse(seo: Pick<BoardSeo, 'indexNowKey'>, init?: SeoFileResponseInit): Response;
@@ -33,4 +55,4 @@ declare function googleSiteVerificationMeta(token: string | null | undefined): {
33
55
  content: string;
34
56
  } | null;
35
57
 
36
- export { ADS_TXT_CACHE_CONTROL, INDEXNOW_CACHE_CONTROL, ROBOTS_CACHE_CONTROL, SEO_FILE_CONTENT_TYPE, type SeoFileResponseInit, adsTxtResponse, googleSiteVerificationMeta, indexNowResponse, robotsBody, robotsResponse };
58
+ export { ADS_TXT_CACHE_CONTROL, INDEXNOW_CACHE_CONTROL, ROBOTS_CACHE_CONTROL, type RobotsListingPaths, SEO_FILE_CONTENT_TYPE, type SeoFileResponseInit, adsTxtResponse, googleSiteVerificationMeta, indexNowResponse, robotsBody, robotsResponse };
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-DgFBQTIK.js';
2
- import './_spec-B2aizgxr.js';
1
+ import { B as BoardSeo } from './seo-CYmVKHsu.js';
2
+ import './_spec-CdNZDaaR.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
@@ -18,13 +18,35 @@ declare const INDEXNOW_CACHE_CONTROL = "public, max-age=86400, must-revalidate";
18
18
  type SeoFileResponseInit = {
19
19
  method?: string;
20
20
  };
21
+ /**
22
+ * The board's own listing roots, for boards that rename or localize their
23
+ * URL words (for example `/vacatures` in place of `/jobs`). Each root scopes
24
+ * one group of pagination `Disallow` rules. A missing, root (`/`) or unsafe
25
+ * value keeps the default English root for that group.
26
+ */
27
+ type RobotsListingPaths = {
28
+ jobs?: string | null;
29
+ companies?: string | null;
30
+ talent?: string | null;
31
+ };
21
32
  /** Lines of the hosted robots body (no sitemap line). Exported so tests and hosted can share the literal. */
22
33
  declare function robotsBody(input: {
23
34
  canonicalBase: string;
24
35
  sandbox?: boolean;
36
+ listingPaths?: RobotsListingPaths | null;
37
+ /**
38
+ * Listing roots of the board's secondary languages, served under a
39
+ * language prefix with that language's own URL words (for example
40
+ * `/de/stellen`). Each safe root adds one group of pagination rules after
41
+ * the base ones, in order. A missing, root or unsafe value adds nothing,
42
+ * and a root already listed is not repeated.
43
+ */
44
+ localeListingPaths?: readonly RobotsListingPaths[] | null;
25
45
  }): string;
26
46
  declare function robotsResponse(seo: Pick<BoardSeo, 'canonicalBase'> & {
27
47
  sandbox?: boolean;
48
+ listingPaths?: RobotsListingPaths | null;
49
+ localeListingPaths?: readonly RobotsListingPaths[] | null;
28
50
  }, init?: SeoFileResponseInit): Response;
29
51
  declare function adsTxtResponse(seo: Pick<BoardSeo, 'adsTxt'>, init?: SeoFileResponseInit): Response;
30
52
  declare function indexNowResponse(seo: Pick<BoardSeo, 'indexNowKey'>, init?: SeoFileResponseInit): Response;
@@ -33,4 +55,4 @@ declare function googleSiteVerificationMeta(token: string | null | undefined): {
33
55
  content: string;
34
56
  } | null;
35
57
 
36
- export { ADS_TXT_CACHE_CONTROL, INDEXNOW_CACHE_CONTROL, ROBOTS_CACHE_CONTROL, SEO_FILE_CONTENT_TYPE, type SeoFileResponseInit, adsTxtResponse, googleSiteVerificationMeta, indexNowResponse, robotsBody, robotsResponse };
58
+ export { ADS_TXT_CACHE_CONTROL, INDEXNOW_CACHE_CONTROL, ROBOTS_CACHE_CONTROL, type RobotsListingPaths, SEO_FILE_CONTENT_TYPE, type SeoFileResponseInit, adsTxtResponse, googleSiteVerificationMeta, indexNowResponse, robotsBody, robotsResponse };
package/dist/seo-files.js CHANGED
@@ -35,25 +35,54 @@ var SEO_FILE_CONTENT_TYPE = "text/plain; charset=utf-8";
35
35
  var ROBOTS_CACHE_CONTROL = "public, max-age=600, s-maxage=14400, stale-while-revalidate=86400";
36
36
  var ADS_TXT_CACHE_CONTROL = "public, max-age=3600, must-revalidate";
37
37
  var INDEXNOW_CACHE_CONTROL = "public, max-age=86400, must-revalidate";
38
+ var DEFAULT_LISTING_PATHS = {
39
+ jobs: "/jobs",
40
+ companies: "/companies",
41
+ talent: "/talent"
42
+ };
43
+ var SAFE_LISTING_ROOT = /^(?:\/[^\s/*$?#]+)+$/;
44
+ function safeListingRoot(value) {
45
+ if (typeof value !== "string") return null;
46
+ const root = value.replace(/\/+$/, "");
47
+ return SAFE_LISTING_ROOT.test(root) ? root : null;
48
+ }
49
+ function listingRoot(value, fallback) {
50
+ return safeListingRoot(value) ?? fallback;
51
+ }
52
+ var LISTING_GROUPS = ["jobs", "companies", "talent"];
53
+ function paginationRules(root) {
54
+ return [
55
+ `Disallow: ${root}?page=`,
56
+ `Disallow: ${root}*&page=`,
57
+ `Disallow: ${root}/*?page=`
58
+ ];
59
+ }
38
60
  function robotsBody(input) {
39
61
  if (input.sandbox) {
40
62
  return ["User-Agent: *", "Disallow: /", ""].join("\n");
41
63
  }
42
64
  const canonicalBase = input.canonicalBase.replace(/\/+$/, "");
65
+ const paths = input.listingPaths ?? {};
66
+ const baseRoots = LISTING_GROUPS.map(
67
+ (group) => listingRoot(paths[group], DEFAULT_LISTING_PATHS[group])
68
+ );
69
+ const seen = new Set(baseRoots);
70
+ const localeRoots = [];
71
+ for (const locale of input.localeListingPaths ?? []) {
72
+ for (const group of LISTING_GROUPS) {
73
+ const root = safeListingRoot(locale[group]);
74
+ if (root === null || seen.has(root)) continue;
75
+ seen.add(root);
76
+ localeRoots.push(root);
77
+ }
78
+ }
43
79
  return [
44
80
  "User-Agent: *",
45
81
  "Allow: /",
46
82
  "Disallow: /go/",
47
83
  "Disallow: /t/",
48
- "Disallow: /jobs?page=",
49
- "Disallow: /jobs*&page=",
50
- "Disallow: /jobs/*?page=",
51
- "Disallow: /companies?page=",
52
- "Disallow: /companies*&page=",
53
- "Disallow: /companies/*?page=",
54
- "Disallow: /talent?page=",
55
- "Disallow: /talent*&page=",
56
- "Disallow: /talent/*?page=",
84
+ ...baseRoots.flatMap(paginationRules),
85
+ ...localeRoots.flatMap(paginationRules),
57
86
  "",
58
87
  `Sitemap: ${canonicalBase}/sitemap.xml`,
59
88
  ""
@@ -61,7 +90,12 @@ function robotsBody(input) {
61
90
  }
62
91
  function robotsResponse(seo, init) {
63
92
  return textFileResponse(
64
- robotsBody({ canonicalBase: seo.canonicalBase, sandbox: seo.sandbox }),
93
+ robotsBody({
94
+ canonicalBase: seo.canonicalBase,
95
+ sandbox: seo.sandbox,
96
+ listingPaths: seo.listingPaths,
97
+ localeListingPaths: seo.localeListingPaths
98
+ }),
65
99
  ROBOTS_CACHE_CONTROL,
66
100
  init
67
101
  );