Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 112 additions & 19 deletions .eleventy.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,94 @@
function toValidDate(value) {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}

function sortByRecent(a, b) {
const aDate = toValidDate(a.data.posted_date) || toValidDate(a.date) || new Date(0);
const bDate = toValidDate(b.data.posted_date) || toValidDate(b.date) || new Date(0);
return bDate - aDate;
}

function isLiveJob(data) {
const status = String(data.status || "published").toLowerCase();
if (["draft", "expired", "filled", "archived"].includes(status)) return false;

const expires = toValidDate(data.expires_date);
if (!expires) return true;

const today = new Date();
today.setHours(0, 0, 0, 0);
return expires >= today;
}

function asArray(value) {
if (Array.isArray(value)) return value;
if (value === undefined || value === null || value === "") return [];
return [value];
}

function normalizedSet(values) {
return new Set(asArray(values).map((item) => String(item).toLowerCase()));
}

function overlapScore(sourceSet, values, weight) {
return asArray(values).reduce((score, value) => {
return score + (sourceSet.has(String(value).toLowerCase()) ? weight : 0);
}, 0);
}

module.exports = function (eleventyConfig) {
eleventyConfig.ignores.add("README.md");
eleventyConfig.ignores.add("AGENTS.md");
eleventyConfig.ignores.add("CLAUDE.md");
eleventyConfig.ignores.add("CONTRIBUTING.md");
eleventyConfig.ignores.add("CODE_OF_CONDUCT.md");
eleventyConfig.ignores.add("snapshot-nav.md");
eleventyConfig.ignores.add(".impeccable.md");
eleventyConfig.ignores.add(".github/**");
eleventyConfig.ignores.add(".agent/**");
eleventyConfig.ignores.add(".agents/**");
eleventyConfig.ignores.add(".claude/**");
eleventyConfig.ignores.add(".crush/**");
eleventyConfig.ignores.add(".kiro/**");
eleventyConfig.ignores.add("engineers/_template.md");
eleventyConfig.ignores.add("jobs/_template.md");
eleventyConfig.ignores.add("jobs/**/_template.md");

eleventyConfig.addCollection("engineers", function (api) {
return api
.getFilteredByGlob("engineers/*.md")
.filter((item) => item.page.fileSlug !== "_template");
});

eleventyConfig.addCollection("allJobs", function (api) {
return api
.getFilteredByGlob("jobs/**/*.md")
.filter((item) => !String(item.page.fileSlug || "").startsWith("_"))
.sort(sortByRecent);
});

eleventyConfig.addCollection("jobs", function (api) {
return api
.getFilteredByGlob("jobs/**/*.md")
.filter((item) => !String(item.page.fileSlug || "").startsWith("_"))
.filter((item) => isLiveJob(item.data))
.sort(sortByRecent);
});

eleventyConfig.addPassthroughCopy("site/assets");
eleventyConfig.addPassthroughCopy({ "site/CNAME": "CNAME" });

eleventyConfig.addFilter("uniqueValues", function (collection, field) {
const seen = new Map();
collection.forEach((item) => {
const arr = item.data[field];
if (Array.isArray(arr)) {
arr.forEach((v) => {
if (v) {
const key = v.toLowerCase();
if (!seen.has(key)) seen.set(key, v);
}
});
}
asArray(item.data[field]).forEach((v) => {
if (v) {
const key = String(v).toLowerCase();
if (!seen.has(key)) seen.set(key, v);
}
});
});
return [...seen.values()].sort((a, b) =>
a.toLowerCase().localeCompare(b.toLowerCase())
Expand All @@ -36,16 +99,13 @@ module.exports = function (eleventyConfig) {
const counts = {};
const canonical = {};
collection.forEach((item) => {
const arr = item.data[field];
if (Array.isArray(arr)) {
arr.forEach((v) => {
if (v) {
const key = v.toLowerCase();
if (!canonical[key]) canonical[key] = v;
counts[key] = (counts[key] || 0) + 1;
}
});
}
asArray(item.data[field]).forEach((v) => {
if (v) {
const key = String(v).toLowerCase();
if (!canonical[key]) canonical[key] = v;
counts[key] = (counts[key] || 0) + 1;
}
});
});
return Object.entries(counts)
.sort((a, b) => b[1] - a[1])
Expand All @@ -70,6 +130,39 @@ module.exports = function (eleventyConfig) {
return social.urlPrefix ? social.urlPrefix + value : value;
});

eleventyConfig.addFilter("readableDate", function (value) {
const date = toValidDate(value);
if (!date) return value;
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
year: "numeric"
}).format(date);
});

eleventyConfig.addFilter("relatedEngineers", function (engineers, specializations, frameworks, languages, limit) {
const specSet = normalizedSet(specializations);
const frameworkSet = normalizedSet(frameworks);
const languageSet = normalizedSet(languages);

return (engineers || [])
.map((engineer) => {
const score =
overlapScore(specSet, engineer.data.specializations, 4) +
overlapScore(frameworkSet, engineer.data.frameworks, 2) +
overlapScore(languageSet, engineer.data.languages, 1);

return { engineer, score };
})
.filter((item) => item.score > 0)
.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
return a.engineer.data.name.localeCompare(b.engineer.data.name);
})
.slice(0, limit || 3)
.map((item) => item.engineer);
});

return {
dir: {
input: ".",
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ on:
branches: [main]
paths:
- 'engineers/*.md'
- 'jobs/**'
- 'site/**'
- 'scripts/**'
- '.eleventy.js'
- 'package.json'
- 'package-lock.json'
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/import-jobs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Import Jobs

on:
schedule:
- cron: '17 11 * * *'
workflow_dispatch:

jobs:
import-jobs:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Import jobs
env:
GREENHOUSE_BOARDS: ${{ secrets.GREENHOUSE_BOARDS }}
ASHBY_JOB_BOARDS: ${{ secrets.ASHBY_JOB_BOARDS }}
run: npm run import:jobs

- name: Commit and push changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add jobs
git diff --staged --quiet || git commit -m "docs: refresh imported jobs"
git pull --rebase origin main
git push

- name: Notify Slack of new jobs
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: node scripts/notify-slack.js
96 changes: 80 additions & 16 deletions .github/workflows/process-submission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,55 @@ jobs:
const issue = context.payload.issue;
const body = issue.body || '';

// --- Extract markdown from code fence ---
const match = body.match(/<!-- PROFILE_SUBMISSION -->\s*```yaml\n([\s\S]*?)```/);
if (!match) {
function parseSubmissionBody(issueBody) {
const encodedMatch = issueBody.match(
/<!-- PROFILE_SUBMISSION_START -->\s*([A-Za-z0-9+/=\s]+?)\s*<!-- PROFILE_SUBMISSION_END -->/
);

if (encodedMatch) {
const encoded = encodedMatch[1].replace(/\s+/g, '');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');
const payload = JSON.parse(decoded);
if (!payload || typeof payload.markdown !== 'string' || !payload.markdown.trim()) {
throw new Error('Submission payload did not contain markdown.');
}
return payload.markdown;
}

const legacyMatch = issueBody.match(/<!-- PROFILE_SUBMISSION -->\s*```yaml\n([\s\S]*?)```/);
if (legacyMatch) {
return legacyMatch[1];
}

return null;
}

let content;
try {
content = parseSubmissionBody(body);
} catch (error) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: '❌ We could not decode the submission payload in this issue. Please return to the [submission form](https://directory.grcengclub.com/submit/), copy a fresh payload, and submit a new issue.'
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned'
});
return;
}

if (!content) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: '❌ Could not parse profile content from this issue. Please use the [submission form](https://directory.grcengclub.com/submit/) to resubmit.'
body: '❌ Could not find a valid submission payload in this issue. Please return to the [submission form](https://directory.grcengclub.com/submit/), paste the copied payload into the issue body, and submit a new issue.'
});
await github.rest.issues.update({
owner: context.repo.owner,
Expand All @@ -39,7 +80,6 @@ jobs:
});
return;
}
const content = match[1];

// --- Parse required fields from YAML frontmatter ---
const ghMatch = content.match(/^github:\s*"([^"]+)"/m);
Expand All @@ -59,7 +99,8 @@ jobs:
});
return;
}
const username = ghMatch[1];
const username = ghMatch[1].trim();
const normalizedUsername = username.toLowerCase();

const nameMatch = content.match(/^name:\s*"([^"]+)"/m);
const profileName = nameMatch ? nameMatch[1] : username;
Expand Down Expand Up @@ -87,7 +128,7 @@ jobs:
return;
}

const filename = `engineers/${username}.md`;
const filename = `engineers/${normalizedUsername}.md`;

// --- Label the issue for bookkeeping ---
try {
Expand All @@ -101,19 +142,45 @@ jobs:
// Label may not exist yet — not critical
}

// --- Check if profile already exists ---
try {
await github.rest.repos.getContent({
async function findExistingProfileByGithub() {
const entries = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: filename,
path: 'engineers',
ref: 'main'
});

for (const entry of entries.data) {
if (entry.type !== 'file' || !entry.name.endsWith('.md') || entry.name === '_template.md') {
continue;
}

const file = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: entry.path,
ref: 'main'
});

const fileContent = Buffer.from(file.data.content, 'base64').toString('utf8');
const fileGithubMatch = fileContent.match(/^github:\s*"?(.*?)"?$/m);
if (!fileGithubMatch) continue;

if (fileGithubMatch[1].trim().toLowerCase() === normalizedUsername) {
return entry.path;
}
}

return null;
}

const existingProfilePath = await findExistingProfileByGithub();
if (existingProfilePath) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `⚠️ A profile for **@${username}** already exists. If you'd like to update it, please open a pull request editing \`${filename}\` directly.`
body: `⚠️ A profile for **@${username}** already exists at \`${existingProfilePath}\`. If you'd like to update it, please open a pull request editing that file directly.`
});
await github.rest.issues.update({
owner: context.repo.owner,
Expand All @@ -123,9 +190,6 @@ jobs:
state_reason: 'completed'
});
return;
} catch (e) {
if (e.status !== 404) throw e;
// File doesn't exist — proceed
}

// --- Create branch ---
Expand All @@ -135,7 +199,7 @@ jobs:
ref: 'heads/main'
});

const branchName = `profile/${username}`;
const branchName = `profile/${normalizedUsername}`;
try {
await github.rest.git.createRef({
owner: context.repo.owner,
Expand Down
Loading
Loading