Skip to content

Harden v1: working template helpers, hermetic action, canonical v2 schema, real collection plan - #15

Merged
LukasParke merged 6 commits into
mainfrom
v1-hardening
Aug 20, 2026
Merged

LukasParke merged 6 commits into
mainfrom
v1-hardening

Conversation

@LukasParke

Copy link
Copy Markdown
Owner

Agent: implemented the six improvement areas from the codebase review. Six focused commits, one per area.

1. Template data-source helpers now actually render data (85c8231)

Nunjucks globals do not await promises, so github_events, recent_stars, gists, releases, sponsors, devto_posts, rss_feed, and fetch_json all rendered as nothing (verified with a repro before the fix). Data for these helpers is now prefetched before rendering — routed through GitHubClient so profile tokens, retries, and per-profile identity apply — and the helpers are synchronous readers over that store. Template syntax is unchanged. Two additional latent bugs surfaced and were fixed: the root gists context key shadowed the gists() helper (so it could never be called), and the org/gist "extras" were collected, cached, merged, and never exposed anywhere (removed).

2. GitHub Action hardening (d634e62)

  • The action previously installed the whole monorepo and compiled TypeScript on every consumer run, and its git commit step failed on fresh checkouts (no git identity configured).
  • dist-action/index.js is now a committed esbuild bundle; the action just runs it. CI verifies the bundle is fresh with git diff --exit-code.
  • The action configures the github-actions[bot] identity before committing.
  • Consumer workflows need permissions: contents: write (documented in action.yml and the README).

3. Canonical v2 schema + shared merge (7836016) — breaking

GitHubStatsOutput intersected LegacyStats, writing every stat twice (flattened plus a legacy block), and the renderer re-parsed that shape-shifting JSON with ~300 lines of defensive coercion. Multi-profile merging existed twice with different semantics — and the collector's version summed totals while keeping only the first profile's contribution calendar, so calendar/streak and totals disagreed.

  • diffler-schemas now owns the canonical output type, a Zod schema for it (compile-time-checked against the type), the pure aggregation functions, buildPresentationData, and mergeStatsOutputs.
  • The collector writes canonical documents only (no legacy aliases, no legacy block, no topRepos/contributionsCollection).
  • Multi-profile merging happens once, recomputing calendars (per-day sums, weeks re-anchored on Sunday), streaks, repo metrics, and presentation so every derived number stays consistent.
  • The remotion adapter maps schema-valid documents directly from typed fields; pre-canonical v2 files and pre-v2 JSON still fall back to the defensive normalizers (the published LukasParke/stats JSON keeps rendering until it regenerates).

Both packages are pre-release (0.1.0) and ship together, so the breaking change is contained.

4. Collection plan is real now (69dc50e)

analyzeTemplate computed 12 flags the collector ignored. Templates now opt out of the expensive optional REST metrics they do not reference: analyzeTemplate captures dotted paths (so stats.repoMetrics.traffic and repoMetrics.contributorStats are detected), and buildStatsConfig ANDs the plan into includeTraffic/includeRestRepoStats. Explicit collect runs still gather everything.

5. Tests and ergonomics (836c172)

  • 99 diffler + 33 remotion tests, up from 77 + 31; new coverage for the merge (including the calendar fix), plan detection, prefetch, and the canonical adapter path.
  • Vitest resolves workspace sources directly: pnpm test works on a fresh clone (previously failed until pnpm build).
  • Client retry tests use fake timers: suite time ~16s → ~6s.
  • The CI render smoke test consumes a committed, schema-validated fixture (packages/remotion/fixtures/github-user-stats.json) instead of fetching a live file from another repo.

6. Chores (411d82b)

  • git.ts uses execFileSync — a commit message containing quotes or $(...) can no longer execute.
  • Removed axios and octokit from the remotion package (declared, never imported).
  • Consolidated the three export-remotion* commands into one; its output now matches the SourceProps shape the renderer consumes.
  • scripts/bump-version.mjs for lockstep version bumps ahead of the first npm release.
  • README/ROADMAP updated.

Verification

  • pnpm verify (build + typecheck + test) passes; pnpm test:package (packed-CLI smoke in a clean project) passes.
  • The committed bundle was rebuilt after CLI changes and renders via init/render/validate/export-remotion in a scratch project.
  • The render fixture was exercised through the real Remotion pipeline locally (120/120 frames encoded; local ffmpeg lacks libwebp, which CI's apt build has).

Nunjucks globals do not await promises, so github_events, recent_stars,
gists, releases, sponsors, devto_posts, rss_feed, and fetch_json all
rendered as nothing when used in templates.

Data for these helpers is now prefetched before rendering (routed
through GitHubClient so profile tokens, retries, and per-profile
identity apply) and the helpers are synchronous readers over that
prefetched store. Template syntax and helper signatures are unchanged.
…as plumbing

The CollectionPlan computed from the template was mostly decorative:
runStatsCollection always fetched traffic and contributor stats, and the
plan only toggled org/gist extras that were collected, cached, merged,
and then never exposed to templates.

- analyzeTemplate now captures dotted paths, so references like
  stats.repoMetrics.traffic or repoMetrics.contributorStats select the
  optional REST metrics they need
- buildStatsConfig ANDs the plan into includeTraffic/includeRestRepoStats;
  configuration can still disable them, the template decides the default
- removed the organizations/gists extras path entirely (never surfaced;
  gists now flow through the prefetched template helper) and the root
  gists context key that shadowed the gists() helper
The composite action previously installed the whole monorepo and compiled
TypeScript on every consumer run, and its git commit step failed on fresh
checkouts because no git user was configured.

- dist-action/index.js is an esbuild bundle committed to the repo; the
  action now just runs it (CI verifies it is fresh with git diff)
- the action configures the github-actions[bot] identity before committing
- consumer workflows need permissions: contents: write (documented in
  action.yml and the README)
GitHubStatsOutput previously intersected LegacyStats, flattening every
stat twice into the JSON (top level plus a legacy block), and the
renderer re-parsed that shape-shifting document with ~300 lines of
defensive coercion. Multi-profile merging existed twice (collector and
renderer) with different semantics, and the collector's version summed
totals while keeping only the first profile's contribution calendar.

- diffler-schemas now owns the canonical output type, a Zod schema for
  it (compile-time-checked against the type), the pure aggregation
  functions, buildPresentationData, and mergeStatsOutputs
- the collector writes the canonical document only: no flattened legacy
  aliases, no legacy block, no topRepos/contributionsCollection
- multi-profile merge happens once, in the schemas package, recomputing
  calendars (summed per-day, weeks re-anchored on Sunday), streaks, repo
  metrics, and presentation so every derived number stays consistent
- the remotion adapter maps schema-valid documents directly from typed
  fields; sparse pre-canonical v2 files and pre-v2 JSON still fall back
  to the defensive normalizers
- remotion fetchUserStats merges canonical documents with the same
  shared mergeStatsOutputs used by the collector
- fixed remotionInput reading nonexistent repoMetrics.publicRepoCount
  and the example template referencing removed stats.highlights

BREAKING: the stats JSON no longer contains flattened legacy aliases or
the legacy block; both packages are pre-release (0.1.0) and ship together.
…nder fixture

- vitest resolves @lukasparke/diffler-schemas from source, so pnpm test
  works on a fresh clone without building first (the old flow failed
  with 'Failed to resolve entry' until pnpm build ran)
- GitHubClient retry tests use fake timers: suite drops from ~16s to ~6s
- analyzeTemplate covered, including dotted-path detection of traffic
  and contributorStats
- packages/remotion/fixtures/github-user-stats.json is a schema-validated
  canonical fixture; the CI render smoke test and test:render consume it
  via STATS_JSON_FILE instead of fetching a live file from another repo
  (prepare-stats.mjs supports both)
…dd bump script

- git.ts uses execFileSync: a configured commit message containing quotes
  or command substitution can no longer break out of (or into) a shell
- the three export-remotion* commands collapse into one: export-remotion
  writes inline-stats input.json, with an optional --scenes manifest;
  its output now matches the SourceProps shape the renderer consumes
- CLI failures exit 1 with a single-line error instead of an unhandled
  rejection stack
- removed axios and octokit from @lukasparke/diffler-remotion; neither
  was imported anywhere (both were runtime dependencies)
- scripts/bump-version.mjs bumps all packages in lockstep and refreshes
  the lockfile, complementing the tag-driven release workflow
- README documents action permissions and the release flow; ROADMAP
  marks the completed items
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98598c72-1b8f-4cd0-a5a9-fad303e40c0f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@LukasParke
LukasParke merged commit f682b3f into main Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant