Files
santifer--career-ops/providers/workable.mjs
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

104 lines
3.8 KiB
JavaScript

// @ts-check
/** @typedef {import('./_types.js').Provider} Provider */
// Workable provider — hits the public markdown feed at /<slug>/jobs.md.
// Workable's documented JSON API requires an auth token; the markdown feed
// is the only no-auth public surface. Auto-detects from careers_url pattern
// `https://apply.workable.com/<slug>`. A tracked_companies entry can also
// set `provider: workable` explicitly to bypass detection.
const ALLOWED_WORKABLE_HOSTS = new Set(['apply.workable.com']);
function assertWorkableUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`workable: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`workable: URL must use HTTPS: ${url}`);
if (!ALLOWED_WORKABLE_HOSTS.has(parsed.hostname)) {
throw new Error(`workable: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_WORKABLE_HOSTS].join(', ')}`);
}
return url;
}
function resolveFeedUrl(entry) {
const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return null;
let parsed;
try {
parsed = new URL(raw);
} catch {
return null;
}
if (parsed.protocol !== 'https:') return null;
if (parsed.hostname !== 'apply.workable.com') return null;
const slug = parsed.pathname.split('/').filter(Boolean)[0];
if (!slug) return null;
return `https://apply.workable.com/${slug}/jobs.md`;
}
/** @type {Provider} */
export default {
id: 'workable',
detect(entry) {
const feedUrl = resolveFeedUrl(entry);
return feedUrl ? { url: feedUrl } : null;
},
async fetch(entry, ctx) {
const feedUrl = resolveFeedUrl(entry);
if (!feedUrl) throw new Error(`workable: cannot derive feed URL for ${entry.name}`);
assertWorkableUrl(feedUrl);
// redirect:'error' prevents SSRF via server-side redirects; combined with
// assertWorkableUrl above it guarantees the final hostname stays in the allowlist.
const text = await ctx.fetchText(feedUrl, { redirect: 'error' });
return parseWorkableMarkdown(text, entry.name);
},
};
/**
* Parse Workable's public markdown feed. Exported as a named export for unit
* tests. The feed exposes a table:
* | Title | Department | Location | Type | Salary | Posted | Details |
* where `Details` holds a markdown link
* [View](https://apply.workable.com/<slug>/jobs/view/<id>.md)
* URLs are validated against `https://apply.workable.com/` — off-domain or
* non-HTTPS [View] links are skipped (not emitted).
*
* @param {string} text — markdown body
* @param {string} companyName — value to write into job.company
* @returns {Array<{title: string, url: string, company: string, location: string}>}
*/
export function parseWorkableMarkdown(text, companyName) {
if (typeof text !== 'string') return [];
const jobs = [];
for (const line of text.split('\n')) {
if (!line.startsWith('|') || !line.includes('[View]')) continue;
const cols = line.split('|').map(c => c.trim());
// Cols: ['', title, dept, location, type, salary, posted, '[View](url.md)', '']
if (cols.length < 8) continue;
const title = cols[1];
if (!title || title === 'Title') continue;
const location = cols[3] || '';
const urlMatch = line.match(/\[View\]\(([^)]+)\)/);
let url = urlMatch ? urlMatch[1] : '';
if (url.endsWith('.md')) url = url.slice(0, -3);
if (!url) continue; // skip rows with no resolvable URL (e.g., malformed [View] link)
// Validate the extracted URL — must parse as https://apply.workable.com/...
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'https:' || parsedUrl.hostname !== 'apply.workable.com') continue;
url = parsedUrl.href;
} catch {
continue;
}
jobs.push({ title, url, location, company: companyName });
}
return jobs;
}