chore: reorganize repo structure and update docs
- move temp scripts to scripts/, cv files to cv/, jobs.csv to results/ - update index.ts and recheck.ts CSV path to results/jobs.csv - fix imports in scripts/ to use ../config.ts and ../fetch.ts - update CLAUDE.md and README.md to reflect new structure - add plans/ with LinkedIn post drafts - update .gitignore: ignore results/, jobs/, cv binaries, .DS_Store
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bun
|
||||
import { KEYWORDS, fetchJobs, isEnglishContent } from "../fetch.ts";
|
||||
import type { SearchConfig } from "../config.ts";
|
||||
|
||||
// Re-export KEYWORDS isn't in fetch.ts, import from config
|
||||
import { KEYWORDS as CFG_KEYWORDS } from "../config.ts";
|
||||
|
||||
const HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
};
|
||||
|
||||
const SAAS_RE = /\bsaas\b/i;
|
||||
|
||||
async function hasSaaS(id: string): Promise<boolean> {
|
||||
try {
|
||||
await Bun.sleep(350);
|
||||
const res = await fetch(`https://www.linkedin.com/jobs/view/${id}/`, { headers: HEADERS });
|
||||
const html = await res.text();
|
||||
const m = html.match(/class="show-more-less-html__markup[^"]*">([\s\S]{0,8000}?)<\/div>/);
|
||||
const text = m ? m[1].replace(/<[^>]+>/g, " ") : html.replace(/<[^>]+>/g, " ").slice(0, 5000);
|
||||
return SAAS_RE.test(text);
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
const RUBY_KEYWORDS = [
|
||||
"Ruby on Rails", "Rails Engineer", "Rails Developer",
|
||||
"Ruby Engineer", "Ruby Developer", "Ruby",
|
||||
].join(" OR ");
|
||||
|
||||
// --- Part 1: Ruby/Rails in Switzerland ---
|
||||
const rubySearches: SearchConfig[] = [
|
||||
{ label: "Basel Ruby (onsite+hybrid)", keywords: RUBY_KEYWORDS, geoId: "103383283", location: "Basel", workType: ["onsite", "hybrid"], timePosted: "month" },
|
||||
{ label: "Basel Ruby (remote)", keywords: RUBY_KEYWORDS, geoId: "103383283", location: "Basel", workType: ["remote"], timePosted: "month" },
|
||||
{ label: "Zürich Ruby (onsite+hybrid)",keywords: RUBY_KEYWORDS, geoId: "102436504", location: "Zürich, Switzerland", workType: ["onsite", "hybrid"], timePosted: "month" },
|
||||
{ label: "Zürich Ruby (remote)", keywords: RUBY_KEYWORDS, geoId: "102436504", location: "Zürich, Switzerland", workType: ["remote"], timePosted: "month" },
|
||||
{ label: "Switzerland Ruby (remote)", keywords: RUBY_KEYWORDS, location: "Switzerland", workType: ["remote"], timePosted: "month" },
|
||||
{ label: "Bern Ruby", keywords: RUBY_KEYWORDS, location: "Bern, Switzerland", workType: ["onsite", "hybrid", "remote"], timePosted: "month" },
|
||||
];
|
||||
|
||||
// --- Part 2: SaaS-experience jobs in Switzerland ---
|
||||
const saasSearches: SearchConfig[] = [
|
||||
{ label: "Basel SaaS", keywords: CFG_KEYWORDS, geoId: "103383283", location: "Basel", workType: ["onsite", "hybrid"], timePosted: "month" },
|
||||
{ label: "Basel Metro SaaS", keywords: CFG_KEYWORDS, geoId: "90009885", location: "Basel Metropolitan Area", workType: ["onsite", "hybrid"], timePosted: "month" },
|
||||
{ label: "Zürich SaaS", keywords: CFG_KEYWORDS, geoId: "102436504", location: "Zürich, Switzerland", workType: ["onsite", "hybrid"], timePosted: "month" },
|
||||
{ label: "Switzerland SaaS remote", keywords: CFG_KEYWORDS, location: "Switzerland", workType: ["remote"], timePosted: "month" },
|
||||
];
|
||||
|
||||
type Job = { id: string; title: string; company: string; link: string; search: string };
|
||||
|
||||
async function runSearches(searches: SearchConfig[], label: string, filterFn?: (id: string) => Promise<boolean>) {
|
||||
const results: Job[] = [];
|
||||
const seen = new Set<string>();
|
||||
console.log(`\n${"=".repeat(50)}\n${label}\n${"=".repeat(50)}`);
|
||||
|
||||
for (const config of searches) {
|
||||
console.log(`\n[${config.label}]`);
|
||||
const jobs = await fetchJobs(config);
|
||||
if (jobs.length === 0) { console.log(" no jobs found"); continue; }
|
||||
|
||||
let added = 0;
|
||||
for (const job of jobs) {
|
||||
if (seen.has(job.id)) continue;
|
||||
seen.add(job.id);
|
||||
if (filterFn) {
|
||||
process.stdout.write(` ${job.title} @ ${job.company} … `);
|
||||
const pass = await filterFn(job.id);
|
||||
if (!pass) { console.log("no SaaS"); continue; }
|
||||
console.log("SaaS ✓");
|
||||
}
|
||||
results.push({ ...job, search: config.label });
|
||||
added++;
|
||||
}
|
||||
console.log(` => ${added} added`);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const rubyJobs = await runSearches(rubySearches, "RUBY / RAILS IN SWITZERLAND");
|
||||
const saasJobs = await runSearches(saasSearches, "SaaS-EXPERIENCE JOBS IN SWITZERLAND", hasSaaS);
|
||||
|
||||
console.log(`\n\n${"=".repeat(50)}\nRUBY RESULTS: ${rubyJobs.length}\n${"=".repeat(50)}`);
|
||||
for (const j of rubyJobs) console.log(`${j.title} | ${j.company} | ${j.id} | ${j.link}`);
|
||||
|
||||
console.log(`\n${"=".repeat(50)}\nSaaS RESULTS: ${saasJobs.length}\n${"=".repeat(50)}`);
|
||||
for (const j of saasJobs) console.log(`${j.title} | ${j.company} | ${j.id} | ${j.link}`);
|
||||
Reference in New Issue
Block a user