Skip to content
Merged
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
32 changes: 32 additions & 0 deletions .github/workflows/commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Commits

# The changelog and release notes are generated from commit subjects, so the
# format is enforced rather than encouraged. A commit that does not parse is a
# change that vanishes from the release notes.
on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: commits-${{ github.ref }}
cancel-in-progress: true

jobs:
conventional:
name: Conventional commits
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Full history so the base..head range can be walked.
fetch-depth: 0

- name: Validate commit subjects
run: |
bash scripts/check-conventional-commits.sh \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}"
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

# Publishing a release is a deliberate act: push a tag, and this turns the
# conventional commits since the previous tag into grouped release notes.
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Existing tag to (re)build notes for"
required: true

permissions:
contents: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
name: Publish release
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Build release notes
id: notes
run: bash scripts/build-release-notes.sh "${{ inputs.tag || github.ref_name }}" > NOTES.md

- name: Create or update the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag || github.ref_name }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --notes-file NOTES.md
else
gh release create "$TAG" --title "$TAG" --notes-file NOTES.md
fi
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Changelog

Notable changes to JobInsight. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
versions follow [Semantic Versioning](https://semver.org/).

Entries from v0.1.0 onward are generated from Conventional Commits by
`scripts/build-release-notes.sh`.

## [Unreleased]

Nothing yet.

## [0.1.0] — 2026-09-15

First tagged release. The extension version in `manifest.json` and the tag now
agree, so "which build is this" has an answer.

### Added

- Chrome MV3 extension that analyses LinkedIn job postings in place: experience,
education, sponsorship, US citizenship or clearance, a short summary, and
highlighted keywords
- Draggable, resizable overlay; results cached for seven days
- Backend proxy holding the OpenAI key behind Google OAuth, so the key never
ships in extension source
- `/api/health`, advertised by the root document

### Infrastructure

- CI on Node 20 and 22 covering the proxy's origin allowlist: requests with no
Origin, from another extension id, and from an ordinary web page are refused;
the configured extension is not; an oversized body returns 413 rather than
being proxied
- `scripts/validate-manifest.mjs` checks the manifest parses, declares MV3, and
that every file it references exists — a manifest naming a renamed file loads
as a broken extension and nothing else catches it
- `npm audit` at high severity, and a syntax check over every extension script
- Conventional commits enforced; releases generated from them

[Unreleased]: https://github.com/bibeshpyakurel/JobInsight/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/bibeshpyakurel/JobInsight/releases/tag/v0.1.0
57 changes: 57 additions & 0 deletions scripts/build-release-notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Turn the conventional commits between the previous tag and this one into
# grouped Markdown release notes.
set -euo pipefail

TAG="${1:?tag required}"
PREV=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)

if [ -n "$PREV" ]; then
RANGE="${PREV}..${TAG}"
echo "Changes since **${PREV}**."
else
RANGE="$TAG"
echo "First tagged release. Everything below is the history leading up to **${TAG}**."
fi
echo ""

emit() { # <heading> <type-regex>
local heading="$1" types="$2" body
body=$(git log --no-merges --format='%s|%h' "$RANGE" \
| grep -E "^(${types})(\([a-z0-9._/-]+\))?!?: " || true)
[ -z "$body" ] && return 0
echo "### ${heading}"
echo ""
while IFS='|' read -r subject sha; do
# strip the type prefix; keep any scope as a bold lead-in
text=$(echo "$subject" | sed -E 's/^[a-z]+(\([a-z0-9._\/-]+\))?!?: //')
scope=$(echo "$subject" | sed -nE 's/^[a-z]+\(([a-z0-9._\/-]+)\)!?: .*/\1/p')
if [ -n "$scope" ]; then
echo "- **${scope}**: ${text} (\`${sha}\`)"
else
echo "- ${text} (\`${sha}\`)"
fi
done <<< "$body"
echo ""
}

breaking=$(git log --no-merges --format='%s|%h' "$RANGE" | grep -E '^[a-z]+(\([a-z0-9._/-]+\))?!: ' || true)
if [ -n "$breaking" ]; then
echo "### Breaking changes"
echo ""
while IFS='|' read -r subject sha; do
echo "- ${subject} (\`${sha}\`)"
done <<< "$breaking"
echo ""
fi

emit "Features" "feat"
emit "Fixes" "fix"
emit "Performance" "perf"
emit "Documentation" "docs"
emit "Build and CI" "build|ci"
emit "Internal" "refactor|test|chore|style|revert"

echo "---"
echo ""
echo "_Notes generated from Conventional Commits by \`scripts/build-release-notes.sh\`._"
63 changes: 63 additions & 0 deletions scripts/check-conventional-commits.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Validate that every commit introduced by a pull request uses Conventional
# Commits. The changelog is generated from these subjects, so a commit that
# does not parse is a commit that silently disappears from the release notes.
#
# Usage: check-conventional-commits.sh <base-sha> <head-sha>
set -euo pipefail

BASE="${1:?base sha required}"
HEAD="${2:?head sha required}"

TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
# type(optional scope)!: summary — summary must be non-empty and lowercase-ish
PATTERN="^(${TYPES})(\([a-z0-9._/-]+\))?!?: .+"

fail=0
count=0

# Merge commits are generated by GitHub, not written by a human — skip them.
while IFS= read -r sha; do
[ -z "$sha" ] && continue
subject=$(git log -1 --format=%s "$sha")
count=$((count + 1))

if [[ "$subject" =~ $PATTERN ]]; then
if [ "${#subject}" -gt 72 ]; then
echo " ! ${sha:0:8} subject is ${#subject} chars (soft limit 72)"
echo " $subject"
else
echo " ok ${sha:0:8} $subject"
fi
else
echo " x ${sha:0:8} $subject"
fail=1
fi
done < <(git rev-list --no-merges "${BASE}..${HEAD}")

if [ "$count" -eq 0 ]; then
echo "No non-merge commits in range; nothing to validate."
exit 0
fi

if [ "$fail" -ne 0 ]; then
cat <<'MSG'

Some commit subjects are not Conventional Commits.

<type>(<optional scope>): <summary in the imperative>

types: feat fix docs style refactor perf test build ci chore revert
breaking: append ! after the type, or add a BREAKING CHANGE: footer

good: feat(log): accept workouts pasted as free text
fix(auth): stop refreshing an expired session on the server
bad: Update | changes | deployable | fixed stuff

Rewrite them with: git rebase -i <base> (reword the offending commits)
MSG
exit 1
fi

echo ""
echo "All $count commit(s) are Conventional Commits."