Something went wrong. Try again.
because I got bored of customising my CV for every job
Something went wrong. Try again.
TypeScript 92%
MDX 4%
Shell <1%
CSS <1%
Handlebars <1%
Dockerfile <1%
JavaScript <1%
<1%
HTML <1%
Commits 349
README.md
cv-generator #
AI-assisted CV builder. Parses incoming CVs (PDF, GDPR export, manual entry), customises them per vacancy, and renders to HTML/PDF via a Playwright worker.
- Issue tracker: Linear
CVG(workspaceriotbyte, initiative Ship CV Generator) - Remote:
git@tangled.sh:mokkenstorm.dev/cv-manager(GitHub mirror atriotbyte-com/cv-generator)
Repo layout #
This is a bare repo with worktrees, not a normal clone.
cv-generator/
├── .bare/ bare repo (git objects, refs, config)
├── .git file pointing at .bare
├── .worktrees/ feature branch worktrees (one per CVG-N)
│ └── cvg-<N>-feature-name/ branch: feature/cvg-<N>-feature-name
└── main/ primary worktree, branch: main
All code lives inside worktrees. Don't try to check out files in the container directory itself.
git worktree add .worktrees/cvg-<N>-feature-name -b feature/cvg-<N>-feature-name main
git worktree list
git worktree remove .worktrees/cvg-<N>-feature-name
Monorepo #
pnpm workspaces orchestrated by lerna.
Apps #
| Path | Role |
|---|---|
apps/api |
Thin GraphQL shell: resolvers, types, inputs, DataLoaders, REST sidecars. NestJS 11 + Apollo Server 5 + Express 5. |
apps/worker |
NestJS standalone process. Consumes the project-q queue. No HTTP, no GraphQL. Renders PDFs via Playwright. |
apps/client |
React + React Query + GraphQL codegen. Vite. |
apps/docs |
Documentation site (Vite). |
Packages #
| Path | Role |
|---|---|
packages/core |
Thick NestJS module library: domain entities, services, Prisma, mail, database. Schema lives in packages/core/prisma/. |
packages/auth |
Guards (JwtAuthGuard, VerifiedScopeGuard), passport strategies, AuthModule. Imported by apps/api. (Auth domain entities live in @cv/core/auth.) |
packages/handlers |
Project-q queue handlers (worker-side message → domain dispatch). |
packages/ai-parser |
LLM-driven CV parsing. Provider-agnostic. |
packages/ai-provider |
Self-registering AI provider registry (OpenAI, Anthropic, Ollama, ...). |
packages/cv-renderer |
Handlebars → HTML. HTML only, no Playwright - the worker owns rendering to PDF. |
packages/handlebars |
Shared Handlebars helpers/partials. |
packages/file-storage |
File backend abstraction (local, S3-compatible). |
packages/file-upload |
Upload pipeline primitives. |
packages/mail |
Transactional mail. |
packages/routing |
Shared React Router config / link helpers. |
packages/ui |
Shared client components (ESM). |
packages/utils |
compact(), raise(), misc. |
packages/biome-config |
Shared Biome config. |
packages/tsconfig |
Shared tsconfig bases. |
Pipeline #
input (PDF / GDPR ZIP / manual)
→ ai-parser
→ customisation pass
→ cv-renderer (Handlebars → HTML)
→ worker (Playwright → PDF)
Local dev #
pnpm install
pnpm prisma:generate
pnpm db:setup # prisma:deploy + seed
pnpm codegen # prisma:generate + GraphQL codegen + docs MDX (needs `apps/api/schema.gql` on disk)
pnpm dev # all apps in parallel
Local optional LLM:
pnpm llama:start # boot Ollama
pnpm llama:stop
.env* files are permission-blocked by tool settings; consult git show HEAD:.env.example for the template.
CI / deploy #
| File | Purpose |
|---|---|
.github/workflows/ci.yml |
Lint, typecheck, build, audit on every push. |
.github/workflows/release.yml |
Tag push → build & push server/worker images to GHCR via ci/build-images.sh. |
.github/workflows/deploy.yml |
Manual dispatch on a tag → kubectl set image on the production cluster. |
.github/workflows/deploy-client.yml |
Tag push → deploy SPA to Cloudflare Pages via ci/deploy-client.sh. |
.github/workflows/create-release.yml |
Tag bookkeeping. |
ci/pipeline.ts |
Local mirror of CI steps (install / prisma / lint / typecheck / build / audit). Run as pnpm tsx ci/pipeline.ts [step]. |
.docker/ |
Production Dockerfiles + pruned workspace manifests (copy-manifests.sh). |
Cluster manifests (Prometheus, ingress, cron jobs, secrets) live in a separate infra repo, not here.
Conventions #
- Commits: Conventional Commits, one-line header, scope is the Linear key (e.g.
feat(cvg-84): move CV parsing onto worker queue). - Branches:
feature/cvg-<N>-feature-namematching the Linear issue. - TypeScript: strict,
exactOptionalPropertyTypesenabled. Usecompact()from@cv/utilsfor Prisma inputs that may containundefined. - Style (TS/JS): functional patterns, guard clauses with braces, ternaries for simple conditionals, no
if/elseladders, noforloops, no inline comments (code should be self-documenting). Biome enforces. - GraphQL codegen needs a running API (
localhost:3000/graphql) for schema introspection. - Async jobs run on the project-q queue. The API enqueues, the worker handles. Job status persists to the
AsyncJobPrisma model. - Zod 4: import from
"zod/v4".z.object()is strict by default; usez.looseObject()for passthrough. - Prisma 7: import error classes directly, not via the
Prisma.namespace (TS 5.9 fails to narrow through the re-export).