Skip to content

Commit e154ed4

Browse files
committed
feat: sync neetcode submissions into blog posts
1 parent cea06cd commit e154ed4

6 files changed

Lines changed: 1023 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Sync NeetCode Post
2+
3+
on:
4+
repository_dispatch:
5+
types:
6+
- neetcode_submission_synced
7+
workflow_dispatch:
8+
inputs:
9+
problem_slug:
10+
description: Problem slug from NeetCode submissions
11+
required: true
12+
type: string
13+
language:
14+
description: Submission language
15+
required: true
16+
type: string
17+
submission_path:
18+
description: Path to the submission file in the source repository
19+
required: true
20+
type: string
21+
source_sha:
22+
description: Commit SHA in the source repository
23+
required: true
24+
type: string
25+
problem_title:
26+
description: Optional resolved problem title
27+
required: false
28+
type: string
29+
synced_at:
30+
description: Optional sync timestamp in ISO-8601
31+
required: false
32+
type: string
33+
34+
permissions:
35+
contents: write
36+
37+
concurrency:
38+
group: neetcode-blog-sync
39+
cancel-in-progress: false
40+
41+
jobs:
42+
sync:
43+
runs-on: ubuntu-latest
44+
45+
steps:
46+
- name: Checkout release branch
47+
uses: actions/checkout@v4
48+
with:
49+
ref: release
50+
51+
- name: Resolve dispatch payload
52+
id: payload
53+
env:
54+
INPUT_PROBLEM_SLUG: ${{ inputs.problem_slug }}
55+
INPUT_LANGUAGE: ${{ inputs.language }}
56+
INPUT_SUBMISSION_PATH: ${{ inputs.submission_path }}
57+
INPUT_SOURCE_SHA: ${{ inputs.source_sha }}
58+
INPUT_PROBLEM_TITLE: ${{ inputs.problem_title }}
59+
INPUT_SYNCED_AT: ${{ inputs.synced_at }}
60+
PAYLOAD_PROBLEM_SLUG: ${{ github.event.client_payload.problem_slug }}
61+
PAYLOAD_LANGUAGE: ${{ github.event.client_payload.language }}
62+
PAYLOAD_SUBMISSION_PATH: ${{ github.event.client_payload.submission_path }}
63+
PAYLOAD_SOURCE_SHA: ${{ github.event.client_payload.source_sha }}
64+
PAYLOAD_SOURCE_REPO: ${{ github.event.client_payload.source_repo }}
65+
PAYLOAD_PROBLEM_TITLE: ${{ github.event.client_payload.problem_title }}
66+
PAYLOAD_SYNCED_AT: ${{ github.event.client_payload.synced_at }}
67+
run: |
68+
set -euo pipefail
69+
70+
problem_slug="${INPUT_PROBLEM_SLUG:-${PAYLOAD_PROBLEM_SLUG:-}}"
71+
language="${INPUT_LANGUAGE:-${PAYLOAD_LANGUAGE:-}}"
72+
submission_path="${INPUT_SUBMISSION_PATH:-${PAYLOAD_SUBMISSION_PATH:-}}"
73+
source_sha="${INPUT_SOURCE_SHA:-${PAYLOAD_SOURCE_SHA:-}}"
74+
source_repo="${PAYLOAD_SOURCE_REPO:-devbattery/neetcode-submissions}"
75+
problem_title="${INPUT_PROBLEM_TITLE:-${PAYLOAD_PROBLEM_TITLE:-}}"
76+
synced_at="${INPUT_SYNCED_AT:-${PAYLOAD_SYNCED_AT:-}}"
77+
78+
if [ -z "$problem_slug" ] || [ -z "$language" ] || [ -z "$submission_path" ] || [ -z "$source_sha" ]; then
79+
echo "Missing required NeetCode sync payload." >&2
80+
exit 1
81+
fi
82+
83+
if [ -z "$synced_at" ]; then
84+
synced_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
85+
fi
86+
87+
{
88+
printf 'problem_slug=%s\n' "$problem_slug"
89+
printf 'language=%s\n' "$language"
90+
printf 'submission_path=%s\n' "$submission_path"
91+
printf 'source_sha=%s\n' "$source_sha"
92+
printf 'source_repo=%s\n' "$source_repo"
93+
printf 'problem_title=%s\n' "$problem_title"
94+
printf 'synced_at=%s\n' "$synced_at"
95+
} >> "$GITHUB_OUTPUT"
96+
97+
- name: Set up Node.js
98+
uses: actions/setup-node@v4
99+
with:
100+
node-version: 20
101+
102+
- name: Checkout NeetCode source repository
103+
uses: actions/checkout@v4
104+
with:
105+
repository: ${{ steps.payload.outputs.source_repo }}
106+
ref: ${{ steps.payload.outputs.source_sha }}
107+
path: .neetcode-source
108+
fetch-depth: 0
109+
110+
- name: Sync post markdown
111+
id: sync
112+
env:
113+
BLOG_ROOT: ${{ github.workspace }}
114+
SOURCE_ROOT: ${{ github.workspace }}/.neetcode-source
115+
SOURCE_REPO: ${{ steps.payload.outputs.source_repo }}
116+
PROBLEM_SLUG: ${{ steps.payload.outputs.problem_slug }}
117+
LANGUAGE: ${{ steps.payload.outputs.language }}
118+
SUBMISSION_PATH: ${{ steps.payload.outputs.submission_path }}
119+
SOURCE_SHA: ${{ steps.payload.outputs.source_sha }}
120+
PROBLEM_TITLE: ${{ steps.payload.outputs.problem_title }}
121+
SYNCED_AT: ${{ steps.payload.outputs.synced_at }}
122+
run: |
123+
set -euo pipefail
124+
post_path="$(node scripts/sync-neetcode-post.mjs | tail -n 1)"
125+
printf 'post_path=%s\n' "$post_path" >> "$GITHUB_OUTPUT"
126+
127+
- name: Set up Ruby
128+
uses: ruby/setup-ruby@v1
129+
with:
130+
ruby-version: 3.3
131+
bundler-cache: true
132+
133+
- name: Build site
134+
run: bundle exec jekyll build
135+
136+
- name: Commit and push
137+
id: commit
138+
run: |
139+
set -euo pipefail
140+
git config user.name "github-actions[bot]"
141+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
142+
git add -A _posts
143+
if git diff --cached --quiet; then
144+
echo "committed=false" >> "$GITHUB_OUTPUT"
145+
echo "No NeetCode post changes to commit."
146+
exit 0
147+
fi
148+
git commit -m "chore(neetcode): sync ${{ steps.payload.outputs.problem_slug }}"
149+
git push origin release
150+
echo "committed=true" >> "$GITHUB_OUTPUT"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Dispatch Blog Sync
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
dispatch:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Collect changed submission files
20+
id: changes
21+
env:
22+
BEFORE_SHA: ${{ github.event.before }}
23+
AFTER_SHA: ${{ github.sha }}
24+
run: |
25+
set -euo pipefail
26+
27+
zero_sha="0000000000000000000000000000000000000000"
28+
if [ "${BEFORE_SHA:-}" = "$zero_sha" ] || [ -z "${BEFORE_SHA:-}" ]; then
29+
changed_files="$(git show --pretty='' --name-only "$AFTER_SHA" || true)"
30+
else
31+
changed_files="$(git diff --name-only --diff-filter=AM "$BEFORE_SHA" "$AFTER_SHA" || true)"
32+
fi
33+
34+
submission_files="$(printf '%s\n' "$changed_files" | grep '/submission-.*\.[^/]*$' || true)"
35+
36+
if [ -z "$submission_files" ]; then
37+
echo "has_changes=false" >> "$GITHUB_OUTPUT"
38+
exit 0
39+
fi
40+
41+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
42+
{
43+
echo "files<<EOF"
44+
printf '%s\n' "$submission_files"
45+
echo "EOF"
46+
} >> "$GITHUB_OUTPUT"
47+
48+
- name: Dispatch blog sync events
49+
if: steps.changes.outputs.has_changes == 'true'
50+
env:
51+
BLOG_REPO_DISPATCH_TOKEN: ${{ secrets.BLOG_REPO_DISPATCH_TOKEN }}
52+
SOURCE_REPO: ${{ github.repository }}
53+
SOURCE_SHA: ${{ github.sha }}
54+
SYNCED_AT: ${{ github.event.head_commit.timestamp }}
55+
CHANGED_FILES: ${{ steps.changes.outputs.files }}
56+
run: |
57+
set -euo pipefail
58+
59+
if [ -z "${BLOG_REPO_DISPATCH_TOKEN:-}" ]; then
60+
echo "BLOG_REPO_DISPATCH_TOKEN is required." >&2
61+
exit 1
62+
fi
63+
64+
sync_timestamp="${SYNCED_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
65+
66+
while IFS= read -r file_path; do
67+
[ -z "$file_path" ] && continue
68+
69+
problem_slug="$(basename "$(dirname "$file_path")")"
70+
extension="${file_path##*.}"
71+
72+
case "$extension" in
73+
py) language="python" ;;
74+
js) language="javascript" ;;
75+
ts) language="typescript" ;;
76+
java) language="java" ;;
77+
cpp) language="cpp" ;;
78+
cs) language="csharp" ;;
79+
go) language="go" ;;
80+
rs) language="rust" ;;
81+
kt) language="kotlin" ;;
82+
swift) language="swift" ;;
83+
sql) language="sql" ;;
84+
*) language="$extension" ;;
85+
esac
86+
87+
payload="$(jq -nc \
88+
--arg event_type "neetcode_submission_synced" \
89+
--arg source_repo "$SOURCE_REPO" \
90+
--arg source_sha "$SOURCE_SHA" \
91+
--arg synced_at "$sync_timestamp" \
92+
--arg problem_slug "$problem_slug" \
93+
--arg language "$language" \
94+
--arg submission_path "$file_path" \
95+
'{
96+
event_type: $event_type,
97+
client_payload: {
98+
source_repo: $source_repo,
99+
source_sha: $source_sha,
100+
synced_at: $synced_at,
101+
problem_slug: $problem_slug,
102+
language: $language,
103+
submission_path: $submission_path
104+
}
105+
}')"
106+
107+
curl -fsSL \
108+
-X POST \
109+
-H "Accept: application/vnd.github+json" \
110+
-H "Authorization: Bearer $BLOG_REPO_DISPATCH_TOKEN" \
111+
-H "X-GitHub-Api-Version: 2022-11-28" \
112+
https://api.github.com/repos/devbattery/devbattery.github.io/dispatches \
113+
-d "$payload"
114+
done <<< "$CHANGED_FILES"

0 commit comments

Comments
 (0)