@cavuno/board 4.42.0 → 4.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/{_spec-B2aizgxr.d.mts → _spec-CLxrfYJb.d.mts} +23 -0
- package/dist/{_spec-B2aizgxr.d.ts → _spec-CLxrfYJb.d.ts} +23 -0
- package/dist/bin.mjs +401 -6
- package/dist/{blog-CYHSnNTZ.d.mts → blog-CQ40QX7l.d.mts} +1 -1
- package/dist/{blog-BkACbXY0.d.ts → blog-mlhJ6UcD.d.ts} +1 -1
- package/dist/{board-DWeT9Jbl.d.ts → board-DWFDeICw.d.ts} +2 -2
- package/dist/{board-DK4HmSty.d.mts → board-TOzb2D8c.d.mts} +2 -2
- package/dist/{breadcrumbs-CtKa2zPe.d.ts → breadcrumbs-DJHyzBji.d.ts} +2 -2
- package/dist/{breadcrumbs-nTfIWzRh.d.mts → breadcrumbs-DauM7O7_.d.mts} +2 -2
- package/dist/browser/cavuno-board.global.min.js +1 -1
- package/dist/{chunk-WSO6JN5C.mjs → chunk-DZD3DAEY.mjs} +2 -0
- package/dist/doctor.js +401 -6
- package/dist/doctor.mjs +401 -6
- package/dist/filters.d.mts +2 -2
- package/dist/filters.d.ts +2 -2
- package/dist/format.d.mts +4 -4
- package/dist/format.d.ts +4 -4
- package/dist/index.d.mts +88 -14
- package/dist/index.d.ts +88 -14
- package/dist/index.js +105 -8
- package/dist/index.mjs +104 -9
- package/dist/{jobs-C_eO-WfM.d.mts → jobs-CjQM8EPT.d.mts} +1 -1
- package/dist/{jobs-SAkNWgAB.d.ts → jobs-DZVMzBOQ.d.ts} +1 -1
- package/dist/{search-Cb3Aoa4c.d.mts → search-BtBE6iJO.d.mts} +1 -1
- package/dist/{search-CVcMFOuJ.d.ts → search-CF_wa20h.d.ts} +1 -1
- package/dist/{seo-DPVud7SH.d.mts → seo-0bfk63qi.d.mts} +1 -1
- package/dist/{seo-DgFBQTIK.d.ts → seo-CU9LpWH7.d.ts} +1 -1
- package/dist/seo-files.d.mts +2 -2
- package/dist/seo-files.d.ts +2 -2
- package/dist/seo.d.mts +6 -6
- package/dist/seo.d.ts +6 -6
- package/dist/server.d.mts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +1 -1
- package/dist/sitemap.d.mts +6 -6
- package/dist/sitemap.d.ts +6 -6
- package/dist/sitemap.mjs +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/package.json +1 -1
- package/skills/cavuno-board-post-a-job/SKILL.md +2 -1
- package/skills/manifest.json +1 -1
package/dist/doctor.mjs
CHANGED
|
@@ -37,6 +37,358 @@ function loadSkillCorpus(baseDir) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// src/paths/index.ts
|
|
41
|
+
function encodePathSegment(segment) {
|
|
42
|
+
try {
|
|
43
|
+
return encodeURIComponent(decodeURIComponent(segment)).replace(
|
|
44
|
+
/%3A/gi,
|
|
45
|
+
":"
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function jobDetailPath(companySlug, jobSlug) {
|
|
52
|
+
return `/companies/${encodePathSegment(companySlug)}/jobs/${encodePathSegment(jobSlug)}`;
|
|
53
|
+
}
|
|
54
|
+
function jobsCategoryPath(categorySlug) {
|
|
55
|
+
return `/jobs/${encodePathSegment(categorySlug)}`;
|
|
56
|
+
}
|
|
57
|
+
function jobsSkillPath(skillSlug) {
|
|
58
|
+
return `/jobs/skills/${encodePathSegment(skillSlug)}`;
|
|
59
|
+
}
|
|
60
|
+
function jobsLocationPath(placeSlug) {
|
|
61
|
+
return `/jobs/locations/${encodePathSegment(placeSlug)}`;
|
|
62
|
+
}
|
|
63
|
+
function companyPath(companySlug) {
|
|
64
|
+
return `/companies/${encodePathSegment(companySlug)}`;
|
|
65
|
+
}
|
|
66
|
+
function companyMarketPath(marketSlug) {
|
|
67
|
+
return `/companies/markets/${encodePathSegment(marketSlug)}`;
|
|
68
|
+
}
|
|
69
|
+
function companySalaryPath(companySlug) {
|
|
70
|
+
return `/companies/${encodePathSegment(companySlug)}/salaries`;
|
|
71
|
+
}
|
|
72
|
+
function salaryTitlePath(titleSlug) {
|
|
73
|
+
return `/salaries/titles/${encodePathSegment(titleSlug)}`;
|
|
74
|
+
}
|
|
75
|
+
function salarySkillPath(skillSlug) {
|
|
76
|
+
return `/salaries/skills/${encodePathSegment(skillSlug)}`;
|
|
77
|
+
}
|
|
78
|
+
function salaryLocationPath(placeSlug) {
|
|
79
|
+
return `/salaries/locations/${encodePathSegment(placeSlug)}`;
|
|
80
|
+
}
|
|
81
|
+
function blogPostPath(postSlug) {
|
|
82
|
+
return `/blog/${encodePathSegment(postSlug)}`;
|
|
83
|
+
}
|
|
84
|
+
function blogTagPath(tagSlug) {
|
|
85
|
+
return `/blog/tag/${encodePathSegment(tagSlug)}`;
|
|
86
|
+
}
|
|
87
|
+
function blogAuthorPath(authorSlug) {
|
|
88
|
+
return `/blog/author/${encodePathSegment(authorSlug)}`;
|
|
89
|
+
}
|
|
90
|
+
function talentProfilePath(handle) {
|
|
91
|
+
return `/p/${encodePathSegment(handle)}`;
|
|
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. 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
|
+
talentProfile: ["handle"],
|
|
135
|
+
home: [],
|
|
136
|
+
jobs: [],
|
|
137
|
+
companies: [],
|
|
138
|
+
salaries: [],
|
|
139
|
+
salaryCompanies: [],
|
|
140
|
+
salaryTitles: [],
|
|
141
|
+
salarySkills: [],
|
|
142
|
+
salaryLocations: [],
|
|
143
|
+
blog: [],
|
|
144
|
+
about: [],
|
|
145
|
+
privacyPolicy: [],
|
|
146
|
+
termsOfService: [],
|
|
147
|
+
cookiePolicy: [],
|
|
148
|
+
impressum: [],
|
|
149
|
+
talent: [],
|
|
150
|
+
employers: [],
|
|
151
|
+
alertsManage: [],
|
|
152
|
+
alertsConfirm: []
|
|
153
|
+
};
|
|
154
|
+
var ALL_ROLES = Object.keys(
|
|
155
|
+
ROLE_PARAM_REGISTRY
|
|
156
|
+
);
|
|
157
|
+
var ROLE_SET = new Set(ALL_ROLES);
|
|
158
|
+
var CANONICAL_MANIFEST = {
|
|
159
|
+
version: 1,
|
|
160
|
+
roles: {
|
|
161
|
+
jobDetail: jobDetailPath(":companySlug", ":jobSlug"),
|
|
162
|
+
jobsCategory: jobsCategoryPath(":categorySlug"),
|
|
163
|
+
jobsSkill: jobsSkillPath(":skillSlug"),
|
|
164
|
+
jobsLocation: jobsLocationPath(":placeSlug"),
|
|
165
|
+
company: companyPath(":companySlug"),
|
|
166
|
+
companyMarket: companyMarketPath(":marketSlug"),
|
|
167
|
+
companySalary: companySalaryPath(":companySlug"),
|
|
168
|
+
salaryTitle: salaryTitlePath(":titleSlug"),
|
|
169
|
+
salarySkill: salarySkillPath(":skillSlug"),
|
|
170
|
+
salaryLocation: salaryLocationPath(":placeSlug"),
|
|
171
|
+
blogPost: blogPostPath(":postSlug"),
|
|
172
|
+
blogTag: blogTagPath(":tagSlug"),
|
|
173
|
+
blogAuthor: blogAuthorPath(":authorSlug"),
|
|
174
|
+
talentProfile: talentProfilePath(":handle"),
|
|
175
|
+
home: BOARD_PATHS.home,
|
|
176
|
+
jobs: BOARD_PATHS.jobs,
|
|
177
|
+
companies: BOARD_PATHS.companies,
|
|
178
|
+
salaries: BOARD_PATHS.salaries,
|
|
179
|
+
salaryCompanies: BOARD_PATHS.salaryCompanies,
|
|
180
|
+
salaryTitles: BOARD_PATHS.salaryTitles,
|
|
181
|
+
salarySkills: BOARD_PATHS.salarySkills,
|
|
182
|
+
salaryLocations: BOARD_PATHS.salaryLocations,
|
|
183
|
+
blog: BOARD_PATHS.blog,
|
|
184
|
+
about: BOARD_PATHS.about,
|
|
185
|
+
privacyPolicy: BOARD_PATHS.privacyPolicy,
|
|
186
|
+
termsOfService: BOARD_PATHS.termsOfService,
|
|
187
|
+
cookiePolicy: BOARD_PATHS.cookiePolicy,
|
|
188
|
+
impressum: BOARD_PATHS.impressum,
|
|
189
|
+
talent: BOARD_PATHS.talent,
|
|
190
|
+
employers: BOARD_PATHS.employers,
|
|
191
|
+
alertsManage: "/alerts/manage",
|
|
192
|
+
alertsConfirm: "/alerts/confirm"
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/route-contract/match-history.ts
|
|
197
|
+
var ROLE_INDEX_PATHS = {
|
|
198
|
+
jobDetail: BOARD_PATHS.jobs,
|
|
199
|
+
jobsCategory: BOARD_PATHS.jobs,
|
|
200
|
+
jobsSkill: BOARD_PATHS.jobs,
|
|
201
|
+
jobsLocation: BOARD_PATHS.jobs,
|
|
202
|
+
company: BOARD_PATHS.companies,
|
|
203
|
+
companyMarket: BOARD_PATHS.companies,
|
|
204
|
+
companySalary: BOARD_PATHS.companies,
|
|
205
|
+
salaryTitle: BOARD_PATHS.salaries,
|
|
206
|
+
salarySkill: BOARD_PATHS.salaries,
|
|
207
|
+
salaryLocation: BOARD_PATHS.salaries,
|
|
208
|
+
salaryCompanies: BOARD_PATHS.salaries,
|
|
209
|
+
salaryTitles: BOARD_PATHS.salaries,
|
|
210
|
+
salarySkills: BOARD_PATHS.salaries,
|
|
211
|
+
salaryLocations: BOARD_PATHS.salaries,
|
|
212
|
+
salaries: BOARD_PATHS.salaries,
|
|
213
|
+
blogPost: BOARD_PATHS.blog,
|
|
214
|
+
blogTag: BOARD_PATHS.blog,
|
|
215
|
+
blogAuthor: BOARD_PATHS.blog,
|
|
216
|
+
blog: BOARD_PATHS.blog,
|
|
217
|
+
talentProfile: BOARD_PATHS.talent,
|
|
218
|
+
home: BOARD_PATHS.home,
|
|
219
|
+
jobs: BOARD_PATHS.jobs,
|
|
220
|
+
companies: BOARD_PATHS.companies,
|
|
221
|
+
about: BOARD_PATHS.about,
|
|
222
|
+
privacyPolicy: BOARD_PATHS.privacyPolicy,
|
|
223
|
+
termsOfService: BOARD_PATHS.termsOfService,
|
|
224
|
+
cookiePolicy: BOARD_PATHS.cookiePolicy,
|
|
225
|
+
impressum: BOARD_PATHS.impressum,
|
|
226
|
+
talent: BOARD_PATHS.talent,
|
|
227
|
+
employers: BOARD_PATHS.employers,
|
|
228
|
+
alertsManage: BOARD_PATHS.home,
|
|
229
|
+
alertsConfirm: BOARD_PATHS.home
|
|
230
|
+
};
|
|
231
|
+
function splitSegments(path) {
|
|
232
|
+
const withoutLeading = path.startsWith("/") ? path.slice(1) : path;
|
|
233
|
+
const withoutTrailing = withoutLeading.length > 1 && withoutLeading.endsWith("/") ? withoutLeading.slice(0, -1) : withoutLeading;
|
|
234
|
+
if (withoutTrailing === "") return [];
|
|
235
|
+
return withoutTrailing.split("/");
|
|
236
|
+
}
|
|
237
|
+
var PREFIXED_PARAM_RE = /^([^:]+):([A-Za-z_][A-Za-z0-9_]*)(.*)$/;
|
|
238
|
+
function matchPathToTemplate(template, pathname) {
|
|
239
|
+
const templateSegs = splitSegments(template);
|
|
240
|
+
const pathSegs = splitSegments(pathname);
|
|
241
|
+
if (templateSegs.length !== pathSegs.length) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const params = {};
|
|
245
|
+
for (let i = 0; i < templateSegs.length; i++) {
|
|
246
|
+
const tSeg = templateSegs[i];
|
|
247
|
+
const pSeg = pathSegs[i];
|
|
248
|
+
let name = null;
|
|
249
|
+
let value = pSeg;
|
|
250
|
+
if (tSeg.startsWith(":") && tSeg.length > 1) {
|
|
251
|
+
name = tSeg.slice(1);
|
|
252
|
+
} else {
|
|
253
|
+
const prefixed = PREFIXED_PARAM_RE.exec(tSeg);
|
|
254
|
+
if (prefixed) {
|
|
255
|
+
const [, prefix = "", param = "", suffix = ""] = prefixed;
|
|
256
|
+
if (!pSeg.startsWith(prefix) || !pSeg.endsWith(suffix)) return null;
|
|
257
|
+
name = param;
|
|
258
|
+
value = pSeg.slice(prefix.length, pSeg.length - suffix.length);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (name !== null) {
|
|
262
|
+
if (value.length === 0) return null;
|
|
263
|
+
if (value.includes("/") || value.includes("\\")) return null;
|
|
264
|
+
params[name] = value;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (tSeg !== pSeg) return null;
|
|
268
|
+
}
|
|
269
|
+
return params;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/route-contract/params.ts
|
|
273
|
+
function extractParamNames(template) {
|
|
274
|
+
const names = [];
|
|
275
|
+
const re = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
276
|
+
let match;
|
|
277
|
+
while ((match = re.exec(template)) !== null) {
|
|
278
|
+
const name = match[1];
|
|
279
|
+
if (name !== void 0) names.push(name);
|
|
280
|
+
}
|
|
281
|
+
return names;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/route-contract/validate.ts
|
|
285
|
+
var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:@-]+$/;
|
|
286
|
+
var FIXED_TEXT_ROLES = /* @__PURE__ */ new Set(["talentProfile"]);
|
|
287
|
+
function checkRoleTemplate(role, template) {
|
|
288
|
+
const errors = [];
|
|
289
|
+
if (typeof template !== "string" || template.length === 0) {
|
|
290
|
+
errors.push({
|
|
291
|
+
code: "invalid_template",
|
|
292
|
+
message: `role '${role}' template must be a non-empty string`,
|
|
293
|
+
role
|
|
294
|
+
});
|
|
295
|
+
return errors;
|
|
296
|
+
}
|
|
297
|
+
if (!template.startsWith("/")) {
|
|
298
|
+
errors.push({
|
|
299
|
+
code: "invalid_template",
|
|
300
|
+
message: `role '${role}' template must start with '/', got ${JSON.stringify(template)}`,
|
|
301
|
+
role
|
|
302
|
+
});
|
|
303
|
+
return errors;
|
|
304
|
+
}
|
|
305
|
+
if (/\s/.test(template)) {
|
|
306
|
+
errors.push({
|
|
307
|
+
code: "invalid_template",
|
|
308
|
+
message: `role '${role}' template must not contain whitespace, got ${JSON.stringify(template)}`,
|
|
309
|
+
role
|
|
310
|
+
});
|
|
311
|
+
return errors;
|
|
312
|
+
}
|
|
313
|
+
if (template.includes("?") || template.includes("#")) {
|
|
314
|
+
errors.push({
|
|
315
|
+
code: "template_query_or_fragment",
|
|
316
|
+
message: `role '${role}' template must not contain '?' or '#', got ${JSON.stringify(template)}`,
|
|
317
|
+
role
|
|
318
|
+
});
|
|
319
|
+
return errors;
|
|
320
|
+
}
|
|
321
|
+
if (template.startsWith("//") || template.includes("//")) {
|
|
322
|
+
errors.push({
|
|
323
|
+
code: "template_double_slash",
|
|
324
|
+
message: `role '${role}' template must not contain '//', got ${JSON.stringify(template)}`,
|
|
325
|
+
role
|
|
326
|
+
});
|
|
327
|
+
return errors;
|
|
328
|
+
}
|
|
329
|
+
for (const seg of template.split("/")) {
|
|
330
|
+
if (seg === "." || seg === "..") {
|
|
331
|
+
errors.push({
|
|
332
|
+
code: "template_dot_segment",
|
|
333
|
+
message: `role '${role}' template must not contain '.' or '..' segments, got ${JSON.stringify(template)}`,
|
|
334
|
+
role
|
|
335
|
+
});
|
|
336
|
+
return errors;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (!TEMPLATE_CHARSET_RE.test(template)) {
|
|
340
|
+
errors.push({
|
|
341
|
+
code: "template_illegal_charset",
|
|
342
|
+
message: `role '${role}' template contains illegal characters, got ${JSON.stringify(template)}`,
|
|
343
|
+
role
|
|
344
|
+
});
|
|
345
|
+
return errors;
|
|
346
|
+
}
|
|
347
|
+
const registry = ROLE_PARAM_REGISTRY[role];
|
|
348
|
+
const tokens = extractParamNames(template);
|
|
349
|
+
if (registry.length === 0) {
|
|
350
|
+
if (tokens.length > 0) {
|
|
351
|
+
errors.push({
|
|
352
|
+
code: "static_has_params",
|
|
353
|
+
message: `static role '${role}' template must not contain ':' tokens, got ${template}`,
|
|
354
|
+
role,
|
|
355
|
+
token: tokens[0]
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const registrySet = new Set(registry);
|
|
360
|
+
for (const token of tokens) {
|
|
361
|
+
if (!registrySet.has(token)) {
|
|
362
|
+
errors.push({
|
|
363
|
+
code: "unregistered_param",
|
|
364
|
+
message: `role '${role}' template references unregistered param ':${token}'`,
|
|
365
|
+
role,
|
|
366
|
+
token
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const retained = tokens.filter((t) => registrySet.has(t));
|
|
371
|
+
if (retained.length === 0) {
|
|
372
|
+
errors.push({
|
|
373
|
+
code: "missing_entity_param",
|
|
374
|
+
message: `role '${role}' template must contain at least one registry param (${registry.join(", ")})`,
|
|
375
|
+
role
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (FIXED_TEXT_ROLES.has(role) && isParamsOnly(template)) {
|
|
379
|
+
errors.push({
|
|
380
|
+
code: "invalid_template",
|
|
381
|
+
message: `role '${role}' template must carry fixed text around its params (e.g. '/p/:handle' or '/@:handle'), got ${JSON.stringify(template)}`,
|
|
382
|
+
role
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return errors;
|
|
387
|
+
}
|
|
388
|
+
function isParamsOnly(template) {
|
|
389
|
+
return template.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, "").replace(/\//g, "").length === 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
40
392
|
// src/doctor/checks.ts
|
|
41
393
|
function record(id, tier) {
|
|
42
394
|
return (status, detail) => ({
|
|
@@ -83,8 +435,41 @@ function checkEnv(env) {
|
|
|
83
435
|
return results;
|
|
84
436
|
}
|
|
85
437
|
var JOB_DETAIL_LINK_RE = /href="(\/companies\/[a-z0-9-]+\/jobs\/[a-z0-9-]+)"/i;
|
|
86
|
-
function extractJobDetailLink(html) {
|
|
87
|
-
return html.match(JOB_DETAIL_LINK_RE)?.[1] ?? null;
|
|
438
|
+
function extractJobDetailLink(html, templates) {
|
|
439
|
+
if (!templates) return html.match(JOB_DETAIL_LINK_RE)?.[1] ?? null;
|
|
440
|
+
for (const match of html.matchAll(HREF_PATH_RE)) {
|
|
441
|
+
const path = match[1];
|
|
442
|
+
if (path.endsWith("/")) continue;
|
|
443
|
+
for (const template of templates) {
|
|
444
|
+
const params = matchPathToTemplate(template, path);
|
|
445
|
+
if (params && Object.values(params).every((v) => SLUG_RE.test(v))) {
|
|
446
|
+
return path;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
var HREF_PATH_RE = /href="(\/[^"?#\s]+)"/gi;
|
|
453
|
+
var SLUG_RE = /^[a-z0-9-]+$/i;
|
|
454
|
+
var CANONICAL_JOB_DETAIL_TEMPLATE = CANONICAL_MANIFEST.roles.jobDetail;
|
|
455
|
+
function jobRoutesFromManifest(manifest) {
|
|
456
|
+
const roles = manifest !== null && typeof manifest === "object" && manifest.version === 1 ? manifest.roles : null;
|
|
457
|
+
const role = (name) => {
|
|
458
|
+
const template = roles !== null && typeof roles === "object" ? roles[name] : void 0;
|
|
459
|
+
return typeof template === "string" && checkRoleTemplate(name, template).length === 0 ? template : null;
|
|
460
|
+
};
|
|
461
|
+
const jobs = role("jobs");
|
|
462
|
+
const company = role("company");
|
|
463
|
+
const jobsWord = jobs?.split("/").filter((seg) => seg !== "" && !seg.includes(":")).at(-1);
|
|
464
|
+
const derived = company && jobsWord ? `${company}/${jobsWord}/:jobSlug` : null;
|
|
465
|
+
const detail = role("jobDetail") ?? (derived && checkRoleTemplate("jobDetail", derived).length === 0 ? derived : null);
|
|
466
|
+
return {
|
|
467
|
+
jobsPath: jobs ?? "/jobs",
|
|
468
|
+
jobDetailTemplates: detail && detail !== CANONICAL_JOB_DETAIL_TEMPLATE ? [detail, CANONICAL_JOB_DETAIL_TEMPLATE] : [CANONICAL_JOB_DETAIL_TEMPLATE]
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function describeJobDetailTemplate(template) {
|
|
472
|
+
return template.replace(":companySlug", "{c}").replace(":jobSlug", "{s}").replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, "{$1}");
|
|
88
473
|
}
|
|
89
474
|
var JSON_LD_RE = /<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi;
|
|
90
475
|
function extractJobPostingJsonLd(html) {
|
|
@@ -8384,9 +8769,19 @@ async function confirmsNoPublishedJobs(fetchImpl, env) {
|
|
|
8384
8769
|
return false;
|
|
8385
8770
|
}
|
|
8386
8771
|
}
|
|
8772
|
+
async function readJobRoutes(fetchImpl, base) {
|
|
8773
|
+
const manifest = await probe(fetchImpl, `${base}/.well-known/cavuno.json`);
|
|
8774
|
+
if (!manifest.ok) return jobRoutesFromManifest(null);
|
|
8775
|
+
try {
|
|
8776
|
+
return jobRoutesFromManifest(JSON.parse(manifest.body));
|
|
8777
|
+
} catch {
|
|
8778
|
+
return jobRoutesFromManifest(null);
|
|
8779
|
+
}
|
|
8780
|
+
}
|
|
8387
8781
|
async function probeJobsAndJsonLd(fetchImpl, base, env) {
|
|
8388
|
-
const
|
|
8389
|
-
const
|
|
8782
|
+
const { jobsPath, jobDetailTemplates } = await readJobRoutes(fetchImpl, base);
|
|
8783
|
+
const jobs = await probe(fetchImpl, `${base}${jobsPath}`);
|
|
8784
|
+
const jobLink = jobs.ok ? extractJobDetailLink(jobs.body, jobDetailTemplates) : null;
|
|
8390
8785
|
if (jobs.status === 200 && !jobLink && /<(html|body|main)[\s>]/i.test(jobs.body) && await confirmsNoPublishedJobs(fetchImpl, env)) {
|
|
8391
8786
|
return [
|
|
8392
8787
|
READ.jobs("pass", "listing renders; API confirms zero published jobs"),
|
|
@@ -8395,14 +8790,14 @@ async function probeJobsAndJsonLd(fetchImpl, base, env) {
|
|
|
8395
8790
|
}
|
|
8396
8791
|
const jobsResult = jobs.ok && jobLink ? READ.jobs("pass", `listing renders with job detail links (${jobLink})`) : READ.jobs(
|
|
8397
8792
|
"fail",
|
|
8398
|
-
jobs.ok ?
|
|
8793
|
+
jobs.ok ? `listing renders but contains no ${describeJobDetailTemplate(jobDetailTemplates[0])} detail links` : `listing HTTP ${jobs.status}`
|
|
8399
8794
|
);
|
|
8400
8795
|
if (!jobLink) {
|
|
8401
8796
|
return [
|
|
8402
8797
|
jobsResult,
|
|
8403
8798
|
READ.jsonld(
|
|
8404
8799
|
"skip",
|
|
8405
|
-
jobs.ok ? "not probed \u2014 the listing rendered no job detail link (see read.jobs failure)" :
|
|
8800
|
+
jobs.ok ? "not probed \u2014 the listing rendered no job detail link (see read.jobs failure)" : `not probed \u2014 the ${jobsPath} listing itself failed (see read.jobs failure)`
|
|
8406
8801
|
)
|
|
8407
8802
|
];
|
|
8408
8803
|
}
|
package/dist/filters.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
2
|
-
import './_spec-
|
|
1
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-CjQM8EPT.mjs';
|
|
2
|
+
import './_spec-CLxrfYJb.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@cavuno/board/filters` — the canonical listing-filter vocabulary and URL
|
package/dist/filters.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
2
|
-
import './_spec-
|
|
1
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-DZVMzBOQ.js';
|
|
2
|
+
import './_spec-CLxrfYJb.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@cavuno/board/filters` — the canonical listing-filter vocabulary and URL
|
package/dist/format.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-
|
|
2
|
-
import { C as CustomFieldDefinition } from './board-
|
|
3
|
-
import { C as CustomFieldValues } from './jobs-
|
|
4
|
-
import './_spec-
|
|
1
|
+
export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-DauM7O7_.mjs';
|
|
2
|
+
import { C as CustomFieldDefinition } from './board-TOzb2D8c.mjs';
|
|
3
|
+
import { C as CustomFieldValues } from './jobs-CjQM8EPT.mjs';
|
|
4
|
+
import './_spec-CLxrfYJb.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Date display helpers in the board language (required leading parameter,
|
package/dist/format.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-
|
|
2
|
-
import { C as CustomFieldDefinition } from './board-
|
|
3
|
-
import { C as CustomFieldValues } from './jobs-
|
|
4
|
-
import './_spec-
|
|
1
|
+
export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-DJHyzBji.js';
|
|
2
|
+
import { C as CustomFieldDefinition } from './board-DWFDeICw.js';
|
|
3
|
+
import { C as CustomFieldValues } from './jobs-DZVMzBOQ.js';
|
|
4
|
+
import './_spec-CLxrfYJb.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Date display helpers in the board language (required leading parameter,
|