@cavuno/board 1.44.0 → 2.0.1
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 +22 -0
- package/CONTRIBUTING.md +25 -0
- package/LICENSE +21 -0
- package/README.md +12 -7
- package/SECURITY.md +28 -0
- package/TRADEMARKS.md +16 -0
- package/dist/_spec-sW85fuvT.d.mts +2731 -0
- package/dist/_spec-sW85fuvT.d.ts +2731 -0
- package/dist/bin.mjs +38 -349
- package/dist/{board-DZCw7A6P.d.ts → board-DKXyxwNN.d.ts} +2 -2
- package/dist/{board-BwFW1aU2.d.mts → board-QFeHT4UN.d.mts} +2 -2
- package/dist/doctor.d.mts +6 -19
- package/dist/doctor.d.ts +6 -19
- package/dist/doctor.js +38 -345
- package/dist/doctor.mjs +37 -344
- package/dist/filters.d.mts +5 -5
- package/dist/filters.d.ts +5 -5
- package/dist/format.d.mts +17 -19
- package/dist/format.d.ts +17 -19
- package/dist/go.d.mts +2 -2
- package/dist/go.d.ts +2 -2
- package/dist/go.js +1 -1
- package/dist/go.mjs +1 -1
- package/dist/index.d.mts +16 -18
- package/dist/index.d.ts +16 -18
- package/dist/index.js +39 -28
- package/dist/index.mjs +39 -28
- package/dist/{jobs-BVazo4mA.d.ts → jobs-CuRzTDb1.d.mts} +15 -13
- package/dist/{jobs-CSMshXGV.d.mts → jobs-DTnCZGtb.d.ts} +15 -13
- package/dist/paths.d.mts +6 -6
- package/dist/paths.d.ts +6 -6
- package/dist/paths.js +1 -1
- package/dist/paths.mjs +1 -1
- package/dist/roles-vY0AXhoc.d.mts +3 -3
- package/dist/roles-vY0AXhoc.d.ts +3 -3
- package/dist/route-contract.d.mts +16 -16
- package/dist/route-contract.d.ts +16 -16
- package/dist/route-contract.js +1 -1
- package/dist/route-contract.mjs +1 -1
- package/dist/{salaries-DMQsjLrV.d.ts → salaries-BOpR51Ny.d.mts} +3 -3
- package/dist/{salaries-IJlchnWk.d.mts → salaries-QoNpzRCT.d.ts} +3 -3
- package/dist/{search-B7gr_wOd.d.ts → search-B5ROY6PK.d.ts} +2 -2
- package/dist/{search-B6FJifRE.d.mts → search-f93nrSg3.d.mts} +2 -2
- package/dist/seo.d.mts +17 -18
- package/dist/seo.d.ts +17 -18
- package/dist/server.d.mts +14 -32
- package/dist/server.d.ts +14 -32
- package/dist/sitemap.d.mts +7 -7
- package/dist/sitemap.d.ts +7 -7
- package/dist/sitemap.js +4 -2
- package/dist/sitemap.mjs +4 -2
- package/dist/skills.d.mts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/dist/theme.d.mts +10 -13
- package/dist/theme.d.ts +10 -13
- package/dist/theme.js +1 -1
- package/dist/theme.mjs +1 -1
- package/dist/{ui-copy-CKfFTtLk.d.mts → ui-copy-CL-VN1xb.d.mts} +2 -3
- package/dist/{ui-copy-CKfFTtLk.d.ts → ui-copy-CL-VN1xb.d.ts} +2 -3
- package/dist/well-known.d.mts +3 -3
- package/dist/well-known.d.ts +3 -3
- package/dist/well-known.js +1 -1
- package/dist/well-known.mjs +1 -1
- package/package.json +46 -38
- package/skills/cavuno-board-client/SKILL.md +1 -2
- package/skills/cavuno-board-filters/SKILL.md +2 -2
- package/skills/cavuno-board-format/SKILL.md +1 -1
- package/skills/cavuno-board-i18n/SKILL.md +3 -3
- package/skills/cavuno-board-job-posting/SKILL.md +1 -1
- package/skills/cavuno-board-messaging/SKILL.md +2 -3
- package/skills/cavuno-board-paywall/SKILL.md +1 -1
- package/skills/cavuno-board-salaries/SKILL.md +1 -1
- package/skills/cavuno-board-seo/SKILL.md +4 -6
- package/skills/cavuno-board-server/SKILL.md +1 -1
- package/skills/cavuno-board-setup/SKILL.md +2 -1
- package/skills/cavuno-board-sitemap/SKILL.md +1 -1
- package/skills/cavuno-board-smoke-test/SKILL.md +4 -13
- package/skills/flavors/tanstack-start/SKILL.md +1 -2
- package/skills/manifest.json +1 -1
- package/dist/_spec-CoL3oYpG.d.mts +0 -5296
- package/dist/_spec-CoL3oYpG.d.ts +0 -5296
package/dist/doctor.mjs
CHANGED
|
@@ -187,6 +187,25 @@ function isOffendingSink(lines, index) {
|
|
|
187
187
|
if (!hasCookieWriteSink(stripped)) return false;
|
|
188
188
|
return DOMAIN_SCOPE.test(statementWindow(lines, index));
|
|
189
189
|
}
|
|
190
|
+
var GENERATED_DIRS = /* @__PURE__ */ new Set([
|
|
191
|
+
"paraglide",
|
|
192
|
+
"generated",
|
|
193
|
+
"_generated",
|
|
194
|
+
"__generated__"
|
|
195
|
+
]);
|
|
196
|
+
var GENERATED_FILE = /\.gen\.(?:ts|tsx|js|jsx|mjs|cjs)$/;
|
|
197
|
+
var GENERATED_BANNER = /@generated|generated by|generated from|auto-?generated|do not edit|don't edit/i;
|
|
198
|
+
var BANNER_SCAN_LINES = 5;
|
|
199
|
+
function hasGeneratedBanner(text) {
|
|
200
|
+
const lines = text.split(/\r?\n/);
|
|
201
|
+
const limit = Math.min(lines.length, BANNER_SCAN_LINES);
|
|
202
|
+
for (let i = 0; i < limit; i += 1) {
|
|
203
|
+
const line = lines[i];
|
|
204
|
+
if (!isCommentLine(line)) return false;
|
|
205
|
+
if (GENERATED_BANNER.test(line)) return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
190
209
|
function walkSourceFiles(dir, out) {
|
|
191
210
|
let entries;
|
|
192
211
|
try {
|
|
@@ -196,6 +215,7 @@ function walkSourceFiles(dir, out) {
|
|
|
196
215
|
}
|
|
197
216
|
for (const name of entries) {
|
|
198
217
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
218
|
+
if (GENERATED_DIRS.has(name) || GENERATED_FILE.test(name)) continue;
|
|
199
219
|
const full = join(dir, name);
|
|
200
220
|
let st;
|
|
201
221
|
try {
|
|
@@ -235,6 +255,7 @@ function checkCookieCodecConformance(projectRoot) {
|
|
|
235
255
|
} catch {
|
|
236
256
|
continue;
|
|
237
257
|
}
|
|
258
|
+
if (hasGeneratedBanner(text)) continue;
|
|
238
259
|
const lines = text.split(/\r?\n/);
|
|
239
260
|
for (let i = 0; i < lines.length; i += 1) {
|
|
240
261
|
if (!isOffendingSink(lines, i)) continue;
|
|
@@ -245,7 +266,7 @@ function checkCookieCodecConformance(projectRoot) {
|
|
|
245
266
|
return [
|
|
246
267
|
COOKIE(
|
|
247
268
|
"pass",
|
|
248
|
-
"no domain-scoped Set-Cookie or document.cookie writes in src/"
|
|
269
|
+
"no domain-scoped Set-Cookie or document.cookie writes in app-authored src/ (generated output skipped)"
|
|
249
270
|
)
|
|
250
271
|
];
|
|
251
272
|
}
|
|
@@ -371,86 +392,18 @@ async function runReadProbes(fetchImpl, frontendUrl) {
|
|
|
371
392
|
];
|
|
372
393
|
}
|
|
373
394
|
|
|
374
|
-
// src/doctor/sandbox.ts
|
|
375
|
-
var SANDBOX_GATE = record("sandbox.persona-gate", 1);
|
|
376
|
-
var EXPECTED_PERSONA_COUNT = 8;
|
|
377
|
-
function skipSandboxPersonaGate(reason) {
|
|
378
|
-
return SANDBOX_GATE("skip", reason);
|
|
379
|
-
}
|
|
380
|
-
async function checkSandboxPersonaGate(fetchImpl, env, boardContext) {
|
|
381
|
-
if (boardContext === null) {
|
|
382
|
-
return SANDBOX_GATE(
|
|
383
|
-
"skip",
|
|
384
|
-
"board did not resolve \u2014 fix static.board first"
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
const url = `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}/sandbox/personas`;
|
|
388
|
-
const result = await probe(fetchImpl, url);
|
|
389
|
-
if (boardContext.sandbox !== true) {
|
|
390
|
-
return result.status === 404 ? SANDBOX_GATE(
|
|
391
|
-
"pass",
|
|
392
|
-
"sandbox persona endpoint refuses (404) on a non-sandbox board"
|
|
393
|
-
) : SANDBOX_GATE(
|
|
394
|
-
"fail",
|
|
395
|
-
`sandbox persona endpoint is reachable on a NON-sandbox board (HTTP ${result.status}) \u2014 the capability gate is not holding`
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
if (!result.ok) {
|
|
399
|
-
return SANDBOX_GATE(
|
|
400
|
-
"fail",
|
|
401
|
-
`sandbox persona roster did not resolve (HTTP ${result.status}) \u2014 the persona seed may have failed`
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
let personas;
|
|
405
|
-
let password;
|
|
406
|
-
try {
|
|
407
|
-
const parsed = JSON.parse(result.body);
|
|
408
|
-
personas = parsed.personas;
|
|
409
|
-
password = parsed.password;
|
|
410
|
-
} catch {
|
|
411
|
-
return SANDBOX_GATE(
|
|
412
|
-
"fail",
|
|
413
|
-
"sandbox persona endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
if (!Array.isArray(personas) || personas.length !== EXPECTED_PERSONA_COUNT) {
|
|
417
|
-
return SANDBOX_GATE(
|
|
418
|
-
"fail",
|
|
419
|
-
`expected ${EXPECTED_PERSONA_COUNT} personas, got ${Array.isArray(personas) ? personas.length : "a non-array"} \u2014 the persona seed is incomplete`
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
const unseeded = personas.filter((persona) => persona.seeded !== true);
|
|
423
|
-
if (unseeded.length > 0) {
|
|
424
|
-
const ids = unseeded.map((persona) => typeof persona.id === "string" ? persona.id : "?").join(", ");
|
|
425
|
-
return SANDBOX_GATE(
|
|
426
|
-
"fail",
|
|
427
|
-
`${unseeded.length} of ${EXPECTED_PERSONA_COUNT} personas are not seeded on the board (${ids}) \u2014 the persona seed failed or was purged; run POST /v1/boards/{key}/sandbox/reseed to restore the roster`
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
if (typeof password !== "string" || password.length === 0) {
|
|
431
|
-
return SANDBOX_GATE(
|
|
432
|
-
"fail",
|
|
433
|
-
"roster present but the published password is missing"
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
return SANDBOX_GATE(
|
|
437
|
-
"pass",
|
|
438
|
-
`sandbox roster complete and seeded (${EXPECTED_PERSONA_COUNT} personas)`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
395
|
// src/doctor/theme.ts
|
|
443
396
|
import { createHash } from "crypto";
|
|
444
397
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
445
398
|
import { join as join2 } from "path";
|
|
446
399
|
var THEME = record("static.theme", 1);
|
|
447
|
-
function checkThemeFreshness(projectRoot
|
|
400
|
+
function checkThemeFreshness(projectRoot) {
|
|
448
401
|
const tokensPath = join2(projectRoot, "src/tokens.css");
|
|
449
402
|
if (!existsSync2(tokensPath)) {
|
|
450
403
|
return [
|
|
451
404
|
THEME(
|
|
452
405
|
"skip",
|
|
453
|
-
"
|
|
406
|
+
"no src/tokens.css \u2014 local theme freshness check not applicable"
|
|
454
407
|
)
|
|
455
408
|
];
|
|
456
409
|
}
|
|
@@ -467,232 +420,9 @@ function checkThemeFreshness(projectRoot, context) {
|
|
|
467
420
|
)
|
|
468
421
|
];
|
|
469
422
|
}
|
|
470
|
-
if (!context) {
|
|
471
|
-
return [
|
|
472
|
-
THEME(
|
|
473
|
-
"skip",
|
|
474
|
-
"local derivations fresh; platform snapshot unverified \u2014 board context unavailable"
|
|
475
|
-
)
|
|
476
|
-
];
|
|
477
|
-
}
|
|
478
|
-
if (context.themeSnapshotHash !== tokensHash) {
|
|
479
|
-
return [
|
|
480
|
-
THEME(
|
|
481
|
-
"fail",
|
|
482
|
-
`platform theme snapshot is ${context.themeSnapshotHash ? "stale" : "missing"} \u2014 emails render ${context.themeSnapshotHash ? "an old" : "the legacy"} theme; sync it: \`npm run gen:theme -- --payload | npx convex run boards/themeSnapshot:sync\``
|
|
483
|
-
)
|
|
484
|
-
];
|
|
485
|
-
}
|
|
486
423
|
return [
|
|
487
|
-
THEME(
|
|
488
|
-
"pass",
|
|
489
|
-
`tokens.css \u21C4 resolved module \u21C4 platform snapshot (${tokensHash.slice(0, 12)}\u2026)`
|
|
490
|
-
)
|
|
491
|
-
];
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// src/doctor/writes.ts
|
|
495
|
-
var WRITE = {
|
|
496
|
-
board: record("write.board", 3),
|
|
497
|
-
register: record("write.register", 3),
|
|
498
|
-
login: record("write.login", 3),
|
|
499
|
-
email: record("write.email", 3),
|
|
500
|
-
apply: record("write.apply", 3),
|
|
501
|
-
alert: record("write.alert", 3)
|
|
502
|
-
};
|
|
503
|
-
var DEPENDENT_CHECKS = [
|
|
504
|
-
WRITE.register,
|
|
505
|
-
WRITE.login,
|
|
506
|
-
WRITE.email,
|
|
507
|
-
WRITE.apply,
|
|
508
|
-
WRITE.alert
|
|
509
|
-
];
|
|
510
|
-
function skipWriteProbes(reason) {
|
|
511
|
-
return Object.values(WRITE).map((make) => make("skip", reason));
|
|
512
|
-
}
|
|
513
|
-
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
514
|
-
var RESEND_API_BASE = "https://api.resend.com";
|
|
515
|
-
async function request(fetchImpl, url, init) {
|
|
516
|
-
try {
|
|
517
|
-
const response = await fetchImpl(url, {
|
|
518
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2),
|
|
519
|
-
...init
|
|
520
|
-
});
|
|
521
|
-
let json = null;
|
|
522
|
-
try {
|
|
523
|
-
json = await response.json();
|
|
524
|
-
} catch {
|
|
525
|
-
json = null;
|
|
526
|
-
}
|
|
527
|
-
return { ok: response.ok, status: response.status, json };
|
|
528
|
-
} catch (error) {
|
|
529
|
-
return {
|
|
530
|
-
ok: false,
|
|
531
|
-
status: 0,
|
|
532
|
-
json: { error: error instanceof Error ? error.message : String(error) }
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function post(fetchImpl, url, body, bearer) {
|
|
537
|
-
return request(fetchImpl, url, {
|
|
538
|
-
method: "POST",
|
|
539
|
-
headers: {
|
|
540
|
-
"content-type": "application/json",
|
|
541
|
-
accept: "application/json",
|
|
542
|
-
...bearer ? { authorization: `Bearer ${bearer}` } : {}
|
|
543
|
-
},
|
|
544
|
-
body: JSON.stringify(body)
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
548
|
-
async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, timeoutMs) {
|
|
549
|
-
const deadline = Date.now() + timeoutMs;
|
|
550
|
-
const subjectMarker = `[To: ${recipient}]`;
|
|
551
|
-
do {
|
|
552
|
-
const list = await request(
|
|
553
|
-
fetchImpl,
|
|
554
|
-
`${RESEND_API_BASE}/emails?limit=20`,
|
|
555
|
-
{
|
|
556
|
-
headers: { authorization: `Bearer ${apiKey}` }
|
|
557
|
-
}
|
|
558
|
-
);
|
|
559
|
-
if (!list.ok) {
|
|
560
|
-
return WRITE.email(
|
|
561
|
-
"fail",
|
|
562
|
-
`Resend list returned HTTP ${list.status} \u2014 wrong RESEND_API_KEY?`
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
const items = list.json?.data ?? [];
|
|
566
|
-
const match = items.find(
|
|
567
|
-
(item) => item.subject?.includes(subjectMarker) || item.to?.includes(recipient)
|
|
568
|
-
);
|
|
569
|
-
if (match) {
|
|
570
|
-
return WRITE.email(
|
|
571
|
-
"pass",
|
|
572
|
-
`verification send observed in Resend for ${recipient}`
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
await sleep(pollMs);
|
|
576
|
-
} while (Date.now() < deadline);
|
|
577
|
-
return WRITE.email(
|
|
578
|
-
"fail",
|
|
579
|
-
`no verification send for ${recipient} appeared in Resend within ${timeoutMs}ms`
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
async function runWriteProbes(options) {
|
|
583
|
-
const { fetchImpl } = options;
|
|
584
|
-
const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
|
|
585
|
-
const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
586
|
-
const email = `doctor+${nonce}@cavuno.com`;
|
|
587
|
-
const password = `doctor-probe-${nonce}-A1!`;
|
|
588
|
-
let context = options.boardContext ?? null;
|
|
589
|
-
if (context === null) {
|
|
590
|
-
const r = await request(fetchImpl, base);
|
|
591
|
-
if (!r.ok) {
|
|
592
|
-
return [
|
|
593
|
-
WRITE.board("fail", `board context did not resolve (HTTP ${r.status})`),
|
|
594
|
-
...DEPENDENT_CHECKS.map(
|
|
595
|
-
(make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
|
|
596
|
-
)
|
|
597
|
-
];
|
|
598
|
-
}
|
|
599
|
-
context = r.json ?? {};
|
|
600
|
-
}
|
|
601
|
-
if (context.sandbox !== true) {
|
|
602
|
-
return [
|
|
603
|
-
WRITE.board(
|
|
604
|
-
"fail",
|
|
605
|
-
"refusing write probes: this board is not the platform sandbox (`sandbox` is not true) \u2014 point PUBLIC_CAVUNO_BOARD at the sandbox pk_"
|
|
606
|
-
),
|
|
607
|
-
...DEPENDENT_CHECKS.map(
|
|
608
|
-
(make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
|
|
609
|
-
)
|
|
610
|
-
];
|
|
611
|
-
}
|
|
612
|
-
const results = [
|
|
613
|
-
WRITE.board("pass", "board is the platform sandbox")
|
|
424
|
+
THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
|
|
614
425
|
];
|
|
615
|
-
const registered = await post(fetchImpl, `${base}/auth/register`, {
|
|
616
|
-
role: "candidate",
|
|
617
|
-
method: "emailpass",
|
|
618
|
-
email,
|
|
619
|
-
password,
|
|
620
|
-
displayName: "Doctor Probe"
|
|
621
|
-
});
|
|
622
|
-
const sessionOk = (r) => r.ok && typeof r.json?.accessToken === "string";
|
|
623
|
-
results.push(
|
|
624
|
-
sessionOk(registered) ? WRITE.register("pass", `candidate registered (${email})`) : WRITE.register(
|
|
625
|
-
"fail",
|
|
626
|
-
`register returned HTTP ${registered.status}` + (registered.status === 429 ? " \u2014 rate-limited: register allows 5 signups / 15 min per IP; wait before rerunning" : "")
|
|
627
|
-
)
|
|
628
|
-
);
|
|
629
|
-
let accessToken = null;
|
|
630
|
-
if (sessionOk(registered)) {
|
|
631
|
-
const login = await post(fetchImpl, `${base}/auth/login`, {
|
|
632
|
-
email,
|
|
633
|
-
password
|
|
634
|
-
});
|
|
635
|
-
if (sessionOk(login)) {
|
|
636
|
-
accessToken = login.json.accessToken;
|
|
637
|
-
results.push(WRITE.login("pass", "emailpass login returns a session"));
|
|
638
|
-
} else {
|
|
639
|
-
results.push(WRITE.login("fail", `login returned HTTP ${login.status}`));
|
|
640
|
-
}
|
|
641
|
-
} else {
|
|
642
|
-
results.push(WRITE.login("skip", "not run \u2014 register failed"));
|
|
643
|
-
}
|
|
644
|
-
if (!sessionOk(registered)) {
|
|
645
|
-
results.push(WRITE.email("skip", "not run \u2014 register failed"));
|
|
646
|
-
} else if (!options.resendApiKey) {
|
|
647
|
-
results.push(
|
|
648
|
-
WRITE.email(
|
|
649
|
-
"skip",
|
|
650
|
-
"RESEND_API_KEY not set \u2014 the verification send was NOT verified (platform-operator check)"
|
|
651
|
-
)
|
|
652
|
-
);
|
|
653
|
-
} else {
|
|
654
|
-
results.push(
|
|
655
|
-
await observeVerificationEmail(
|
|
656
|
-
fetchImpl,
|
|
657
|
-
options.resendApiKey,
|
|
658
|
-
email,
|
|
659
|
-
options.emailPollMs ?? 2e3,
|
|
660
|
-
options.emailTimeoutMs ?? 3e4
|
|
661
|
-
)
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
if (!accessToken) {
|
|
665
|
-
results.push(WRITE.apply("skip", "not run \u2014 no session (see above)"));
|
|
666
|
-
} else {
|
|
667
|
-
const listing = await request(fetchImpl, `${base}/jobs?limit=1`);
|
|
668
|
-
const card = listing.json?.data?.[0];
|
|
669
|
-
if (!listing.ok || !card?.slug) {
|
|
670
|
-
results.push(
|
|
671
|
-
WRITE.apply(
|
|
672
|
-
"fail",
|
|
673
|
-
listing.ok ? "the board has no jobs to apply to \u2014 is the sandbox seeded?" : `jobs listing returned HTTP ${listing.status}`
|
|
674
|
-
)
|
|
675
|
-
);
|
|
676
|
-
} else {
|
|
677
|
-
const applied = await post(
|
|
678
|
-
fetchImpl,
|
|
679
|
-
`${base}/jobs/${encodeURIComponent(card.slug)}/apply`,
|
|
680
|
-
{},
|
|
681
|
-
accessToken
|
|
682
|
-
);
|
|
683
|
-
results.push(
|
|
684
|
-
applied.ok && typeof applied.json?.id === "string" ? WRITE.apply("pass", `applied to ${card.slug}`) : WRITE.apply("fail", `apply returned HTTP ${applied.status}`)
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
const alert = await post(fetchImpl, `${base}/job-alerts`, {
|
|
689
|
-
email,
|
|
690
|
-
consent: true
|
|
691
|
-
});
|
|
692
|
-
results.push(
|
|
693
|
-
alert.ok ? WRITE.alert("pass", "job-alert subscription accepted") : WRITE.alert("fail", `job-alerts returned HTTP ${alert.status}`)
|
|
694
|
-
);
|
|
695
|
-
return results;
|
|
696
426
|
}
|
|
697
427
|
|
|
698
428
|
// src/doctor/run.ts
|
|
@@ -731,31 +461,21 @@ async function checkBoardResolves(fetchImpl, env) {
|
|
|
731
461
|
`${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
|
|
732
462
|
);
|
|
733
463
|
if (!result.ok) {
|
|
734
|
-
return
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
),
|
|
739
|
-
context: null
|
|
740
|
-
};
|
|
464
|
+
return STATIC_BOARD(
|
|
465
|
+
"fail",
|
|
466
|
+
`board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
|
|
467
|
+
);
|
|
741
468
|
}
|
|
742
|
-
let parsed = null;
|
|
743
469
|
try {
|
|
744
|
-
parsed = JSON.parse(result.body);
|
|
470
|
+
const parsed = JSON.parse(result.body);
|
|
745
471
|
if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
|
|
746
472
|
} catch {
|
|
747
|
-
return
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
),
|
|
752
|
-
context: null
|
|
753
|
-
};
|
|
473
|
+
return STATIC_BOARD(
|
|
474
|
+
"fail",
|
|
475
|
+
"board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
|
|
476
|
+
);
|
|
754
477
|
}
|
|
755
|
-
return
|
|
756
|
-
result: STATIC_BOARD("pass", "publishable key resolves the board"),
|
|
757
|
-
context: parsed
|
|
758
|
-
};
|
|
478
|
+
return STATIC_BOARD("pass", "publishable key resolves the board");
|
|
759
479
|
}
|
|
760
480
|
var SKILL_ROOTS = [
|
|
761
481
|
".claude/skills",
|
|
@@ -818,46 +538,19 @@ async function runDoctor(options) {
|
|
|
818
538
|
} else {
|
|
819
539
|
results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
|
|
820
540
|
}
|
|
821
|
-
let boardContext = null;
|
|
822
541
|
if (envOk) {
|
|
823
|
-
|
|
824
|
-
boardContext = resolved.context;
|
|
825
|
-
results.push(resolved.result);
|
|
542
|
+
results.push(await checkBoardResolves(fetchImpl, env));
|
|
826
543
|
} else {
|
|
827
544
|
results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
|
|
828
545
|
}
|
|
829
546
|
results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
|
|
830
|
-
results.push(
|
|
831
|
-
...checkThemeFreshness(options.projectRoot ?? process.cwd(), boardContext)
|
|
832
|
-
);
|
|
547
|
+
results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
|
|
833
548
|
results.push(
|
|
834
549
|
...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
|
|
835
550
|
);
|
|
836
|
-
results.push(
|
|
837
|
-
envOk ? await checkSandboxPersonaGate(fetchImpl, env, boardContext) : skipSandboxPersonaGate("env checks failed \u2014 fix them first")
|
|
838
|
-
);
|
|
839
551
|
results.push(
|
|
840
552
|
...options.frontendUrl ? await runReadProbes(fetchImpl, options.frontendUrl) : skipReadProbes("no --frontend <url> given")
|
|
841
553
|
);
|
|
842
|
-
if (!options.sandbox) {
|
|
843
|
-
results.push(
|
|
844
|
-
...skipWriteProbes(
|
|
845
|
-
"write probes not enabled \u2014 run with --sandbox against the platform sandbox"
|
|
846
|
-
)
|
|
847
|
-
);
|
|
848
|
-
} else if (!envOk) {
|
|
849
|
-
results.push(...skipWriteProbes("env checks failed \u2014 fix them first"));
|
|
850
|
-
} else {
|
|
851
|
-
results.push(
|
|
852
|
-
...await runWriteProbes({
|
|
853
|
-
env,
|
|
854
|
-
fetchImpl,
|
|
855
|
-
resendApiKey: options.resendApiKey,
|
|
856
|
-
nonce: options.writeProbeNonce,
|
|
857
|
-
boardContext
|
|
858
|
-
})
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
554
|
return { results, summary: summarize(results) };
|
|
862
555
|
}
|
|
863
556
|
export {
|
package/dist/filters.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
2
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
3
|
-
import './_spec-
|
|
1
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.mjs';
|
|
2
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-CuRzTDb1.mjs';
|
|
3
|
+
import './_spec-sW85fuvT.mjs';
|
|
4
4
|
|
|
5
5
|
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
6
6
|
/**
|
|
@@ -23,7 +23,7 @@ declare const DEFAULT_SORT: JobSort;
|
|
|
23
23
|
declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
|
|
24
24
|
/**
|
|
25
25
|
* Sort-dropdown labels in the board language, resolved from the copy
|
|
26
|
-
* catalog (`uiCopy(locale).jobCard
|
|
26
|
+
* catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
|
|
27
27
|
* (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
|
|
28
28
|
* when the `labels` bag is passed.
|
|
29
29
|
*/
|
|
@@ -40,7 +40,7 @@ interface ListingFilters {
|
|
|
40
40
|
* wire as `companySlug` (`jobs.list` query / `jobs.search` filters).
|
|
41
41
|
*/
|
|
42
42
|
company?: string[];
|
|
43
|
-
/** Result ordering
|
|
43
|
+
/** Result ordering; absent ⇒ `relevance`. */
|
|
44
44
|
sort?: JobSort;
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/filters.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
2
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
3
|
-
import './_spec-
|
|
1
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.js';
|
|
2
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-DTnCZGtb.js';
|
|
3
|
+
import './_spec-sW85fuvT.js';
|
|
4
4
|
|
|
5
5
|
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
6
6
|
/**
|
|
@@ -23,7 +23,7 @@ declare const DEFAULT_SORT: JobSort;
|
|
|
23
23
|
declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
|
|
24
24
|
/**
|
|
25
25
|
* Sort-dropdown labels in the board language, resolved from the copy
|
|
26
|
-
* catalog (`uiCopy(locale).jobCard
|
|
26
|
+
* catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
|
|
27
27
|
* (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
|
|
28
28
|
* when the `labels` bag is passed.
|
|
29
29
|
*/
|
|
@@ -40,7 +40,7 @@ interface ListingFilters {
|
|
|
40
40
|
* wire as `companySlug` (`jobs.list` query / `jobs.search` filters).
|
|
41
41
|
*/
|
|
42
42
|
company?: string[];
|
|
43
|
-
/** Result ordering
|
|
43
|
+
/** Result ordering; absent ⇒ `relevance`. */
|
|
44
44
|
sort?: JobSort;
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/format.d.mts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-
|
|
2
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
3
|
-
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-
|
|
4
|
-
import { C as CustomFieldDefinition } from './board-
|
|
5
|
-
import './_spec-
|
|
1
|
+
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-CuRzTDb1.mjs';
|
|
2
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.mjs';
|
|
3
|
+
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-CL-VN1xb.mjs';
|
|
4
|
+
import { C as CustomFieldDefinition } from './board-QFeHT4UN.mjs';
|
|
5
|
+
import './_spec-sW85fuvT.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Date display helpers in the board language (required leading parameter,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* ) — transcribed from the hosted board's `date-labels.ts` and
|
|
10
|
+
* tested against the hosted behavior.
|
|
11
11
|
*
|
|
12
12
|
* Job cards and the job-detail header render the RELATIVE form
|
|
13
13
|
* (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
|
|
@@ -28,7 +28,7 @@ declare function formatDate(locale: string, value: string | number | null): stri
|
|
|
28
28
|
declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
|
|
29
29
|
/**
|
|
30
30
|
* Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
|
|
31
|
-
* `formatMonthYear
|
|
31
|
+
* `formatMonthYear`. The `T00:00:00` suffix
|
|
32
32
|
* prevents timezone-related off-by-one-month issues on date-only strings.
|
|
33
33
|
*/
|
|
34
34
|
declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
@@ -38,7 +38,7 @@ declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
|
38
38
|
* `PublicJobCard` shape a list card renders — computing the card's
|
|
39
39
|
* pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
|
|
40
40
|
* fields. Board language is required because the computed location label is
|
|
41
|
-
* display text
|
|
41
|
+
* display text.
|
|
42
42
|
*/
|
|
43
43
|
declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
|
|
44
44
|
|
|
@@ -59,7 +59,7 @@ declare function fieldLabel(locale: string, value: string | null, labels?: Board
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Location display labels for job cards and detail pages, in the board
|
|
62
|
-
* language
|
|
62
|
+
* language. The wrapper words
|
|
63
63
|
* ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
|
|
64
64
|
* English-only rendering; place names arrive already board-language from the
|
|
65
65
|
* wire (the API serves canonical, localized labels).
|
|
@@ -84,8 +84,7 @@ declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'rem
|
|
|
84
84
|
* `<title>`/`<description>` sentence FRAMES, keyed by the board language
|
|
85
85
|
* (`board.context().language`), NOT by any visitor locale.
|
|
86
86
|
*
|
|
87
|
-
* Transcribed from the hosted board's `salary-lexicon.ts`
|
|
88
|
-
* ADR-0012) and golden-tested against it in-monorepo (ADR-0057): the two
|
|
87
|
+
* Transcribed from the hosted board's `salary-lexicon.ts` and tested against it : the two
|
|
89
88
|
* copies cannot drift silently. The `de` strings are native-reviewed.
|
|
90
89
|
* Unseeded locales fall back to the English source WORDS while number and
|
|
91
90
|
* currency formatting stays locale-correct.
|
|
@@ -229,7 +228,7 @@ interface SalaryTimeframeOverrides {
|
|
|
229
228
|
}
|
|
230
229
|
/**
|
|
231
230
|
* Format a per-job salary range in the BOARD language
|
|
232
|
-
* (`board.context().language` — required, never defaulted; see
|
|
231
|
+
* (`board.context().language` — required, never defaulted; see ).
|
|
233
232
|
* The fixed words (timeframe, "From"/"Up to") come from
|
|
234
233
|
* `getSalaryLexicon(language)` with precedence owner-override > lexicon >
|
|
235
234
|
* English source. Currency placement is gated: `en` keeps the legacy manual
|
|
@@ -251,7 +250,7 @@ declare function companyIntro(summary: string | null, description: string | null
|
|
|
251
250
|
* permit taxonomy (`GET /v1/taxonomies/remote-permits`), published so a
|
|
252
251
|
* picker on any surface offers exactly the set the API accepts. Mirrors the
|
|
253
252
|
* platform's canonical `COUNTRIES` dataset (`world-countries` minus `KP`,
|
|
254
|
-
* plus `XK`/Kosovo); an
|
|
253
|
+
* plus `XK`/Kosovo); an parity test asserts the mirror cannot
|
|
255
254
|
* drift. Codes only — display names localize at render via
|
|
256
255
|
* `countryOptions(language)`.
|
|
257
256
|
*/
|
|
@@ -263,7 +262,7 @@ interface CountryOption {
|
|
|
263
262
|
}
|
|
264
263
|
/**
|
|
265
264
|
* The complete country option list, display-named in the BOARD language
|
|
266
|
-
*
|
|
265
|
+
* and sorted by that language's
|
|
267
266
|
* collation — the shape a country picker renders. Falls back to the bare
|
|
268
267
|
* code when the runtime cannot resolve the language or name a region.
|
|
269
268
|
*/
|
|
@@ -271,15 +270,14 @@ declare function countryOptions(language: string): CountryOption[];
|
|
|
271
270
|
|
|
272
271
|
/**
|
|
273
272
|
* Resolve a job's opaque `customFieldValues` into ordered, display-ready
|
|
274
|
-
* entries for the "Additional details" section
|
|
275
|
-
*
|
|
276
|
-
* custom-field-display.ts). Pure. Iterates the *definitions* (from board
|
|
273
|
+
* entries for the "Additional details" section, matching the hosted board's
|
|
274
|
+
* display behavior. Pure. Iterates the *definitions* (from board
|
|
277
275
|
* context) in config order, so display order is operator-controlled, orphan
|
|
278
276
|
* values (deleted definitions) are never inspected, and empty values drop per
|
|
279
277
|
* field. Select values resolve their stored option KEY to the current label
|
|
280
278
|
* (rename-safe); an option with no current match is dropped. Booleans stay raw
|
|
281
279
|
* (a discriminated `kind`) — the render layer authors the "Yes"/"No" chrome
|
|
282
|
-
* (English; localized copy is the use-intl
|
|
280
|
+
* (English; localized copy is the use-intl future release).
|
|
283
281
|
*/
|
|
284
282
|
type CustomFieldDisplayEntry = {
|
|
285
283
|
key: string;
|