Skip to content

Commit fa9a229

Browse files
committed
Initial release
Lightweight self-hostable invoice PDF generator. Bun + jsPDF + vanilla TypeScript, 3 runtime deps.
0 parents  commit fa9a229

38 files changed

+9032
-0
lines changed

.dockerignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
node_modules
2+
.git
3+
e2e
4+
test-results
5+
playwright-report
6+
*.md
7+
.github
8+
.dockerignore

.github/workflows/container.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build and Push Container
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
REGISTRY: ghcr.io
11+
IMAGE_NAME: ${{ github.repository }}
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: docker/login-action@v3
24+
with:
25+
registry: ${{ env.REGISTRY }}
26+
username: ${{ github.actor }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
28+
29+
- uses: docker/metadata-action@v5
30+
id: meta
31+
with:
32+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
33+
tags: |
34+
type=sha
35+
type=ref,event=branch
36+
type=raw,value=latest,enable={{is_default_branch}}
37+
38+
- uses: docker/build-push-action@v6
39+
with:
40+
context: .
41+
file: ./Containerfile
42+
push: ${{ github.event_name != 'pull_request' }}
43+
tags: ${{ steps.meta.outputs.tags }}
44+
labels: ${{ steps.meta.outputs.labels }}

.github/workflows/pages.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: oven-sh/setup-bun@v2
24+
with:
25+
bun-version: latest
26+
27+
- run: bun install --frozen-lockfile
28+
29+
- run: bun run build
30+
31+
- uses: actions/upload-pages-artifact@v3
32+
with:
33+
path: public
34+
35+
deploy:
36+
needs: build
37+
runs-on: ubuntu-latest
38+
environment:
39+
name: github-pages
40+
url: ${{ steps.deployment.outputs.page_url }}
41+
steps:
42+
- id: deployment
43+
uses: actions/deploy-pages@v4

.github/workflows/test.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: oven-sh/setup-bun@v2
16+
with:
17+
bun-version: latest
18+
19+
- run: bun install --frozen-lockfile
20+
21+
- run: bun run build
22+
23+
- run: bunx playwright install --with-deps chromium
24+
25+
- run: bun run test
26+
27+
- uses: actions/upload-artifact@v4
28+
if: failure()
29+
with:
30+
name: playwright-report
31+
path: test-results/
32+
retention-days: 7

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules/
2+
dist/
3+
test-results/
4+
playwright-report/
5+
screenshots/
6+
.claude/
7+
*.tgz
8+
.DS_Store
9+
explore-site.mjs
10+
public/js/app.js
11+
public/js/app.js.map

Containerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM oven/bun:1 AS base
2+
WORKDIR /app
3+
4+
# Install dependencies
5+
COPY package.json bun.lock* ./
6+
RUN bun install --frozen-lockfile --production
7+
8+
# Copy source and build
9+
COPY . .
10+
RUN bun run build
11+
12+
# Production
13+
EXPOSE 3000
14+
ENV NODE_ENV=production
15+
CMD ["bun", "run", "src/server.ts"]

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# EasyPDF Lite
2+
3+
A lightweight, self-hostable invoice PDF generator. No cloud, no accounts.
4+
5+
![EasyPDF Lite](screenshot.png)
6+
7+
## Why
8+
9+
[easy-invoice-pdf](https://github.com/VladSez/easy-invoice-pdf) is a solid tool but ships with Next.js 15, React 19, and 50+ npm dependencies. That's a lot of machinery for generating a PDF. This project rebuilds the core functionality with a minimal stack so you can run it anywhere in seconds.
10+
11+
## What you get
12+
13+
- Invoice form with live PDF preview and one-click download
14+
- 10 languages, 45+ currencies, VAT/tax calculations
15+
- Shareable invoice links (URL-encoded, compressed)
16+
- Saved seller/buyer profiles (localStorage)
17+
- Cyrillic/Unicode support (embedded Open Sans fonts)
18+
- 60 e2e tests
19+
20+
## What you don't get
21+
22+
API endpoints, analytics, Sentry, newsletters, about pages, or 200 MB of node_modules.
23+
24+
## Stack
25+
26+
| | Original | Lite |
27+
|-|----------|------|
28+
| Runtime | Node.js + Next.js | Bun |
29+
| UI | React 19 + shadcn/ui | Vanilla TS |
30+
| Styling | Tailwind CSS | Plain CSS |
31+
| PDF | @react-pdf/renderer | jsPDF (browser-side) |
32+
| Dependencies | 50+ | 3 runtime, 4 dev |
33+
| Client payload | ~2 MB | ~555 KB |
34+
35+
## Self-hosting
36+
37+
```bash
38+
docker run -d -p 3000:3000 ghcr.io/h1d/easypdf-lite:latest
39+
```
40+
41+
Open `http://localhost:3000`.
42+
43+
## Development
44+
45+
```bash
46+
bun install
47+
bun run dev # http://localhost:3000
48+
49+
bun run build # bundle for production
50+
bun run start # serve production build
51+
52+
# tests
53+
bunx playwright install --with-deps chromium
54+
bun run test
55+
```
56+
57+
## License
58+
59+
MIT

bun.lock

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)