Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05f094bd13 | ||
|
|
d1c301de83 | ||
|
|
e42af3cd04 | ||
|
|
f3375adecb | ||
|
|
d9e3289f69 | ||
|
|
7c08007a9d | ||
|
|
296f7951ec | ||
|
|
bf1a540fd1 | ||
|
|
e4a574ccd0 | ||
|
|
26ca5c29c3 | ||
|
|
2cd774dab7 | ||
|
|
9cbb30d3ba | ||
|
|
ed42f181ca | ||
|
|
21cadd76fe | ||
|
|
4c771307e0 | ||
|
|
6d54ffa88b | ||
|
|
5042ad9d1f | ||
|
|
3b82aa90f3 | ||
|
|
524a7a6f4e | ||
|
|
36a16f6483 | ||
|
|
e320f3a920 | ||
|
|
d5891ff035 | ||
|
|
891ce60270 | ||
|
|
1b0bb067b6 | ||
|
|
2b3af1f7b7 | ||
|
|
ced765e229 | ||
|
|
023cb4a594 | ||
|
|
a43538c919 | ||
|
|
faba604813 | ||
|
|
f8b3437e33 | ||
|
|
4ac16df3d6 | ||
|
|
4ae3e1a230 | ||
|
|
dcccbcfa9e | ||
|
|
92a3916e76 | ||
|
|
6321e99b9e | ||
|
|
94c953005e | ||
|
|
cf957692fc | ||
|
|
64492ce2f1 | ||
|
|
4165c10a71 | ||
|
|
ef6961c9eb | ||
|
|
13787db34a | ||
|
|
f64daeb89e | ||
|
|
6e447d1bcc | ||
|
|
148d564fe8 | ||
|
|
50ba37a27f | ||
|
|
51c366310e | ||
|
|
4e58ea6c8a | ||
|
|
fd892b0e14 | ||
|
|
dc4830d41b | ||
|
|
480e9a2612 | ||
|
|
4cf5cdd181 | ||
|
|
b9894c687c | ||
|
|
9048702cb8 | ||
|
|
dd94d070af | ||
|
|
e035088269 | ||
|
|
1fbe89bc01 | ||
|
|
4b071f2db7 | ||
|
|
80ca3a8875 | ||
|
|
e95be536fa | ||
|
|
d6287dbd65 | ||
|
|
47abce351b | ||
|
|
8f6c65b7fd | ||
|
|
5a88ac12bf | ||
|
|
118004b3d3 | ||
|
|
6a8dd480cb | ||
|
|
5d8126d4b0 | ||
|
|
8566a9fd0f | ||
|
|
0d0bc61582 | ||
|
|
e3af637100 | ||
|
|
f752145c57 | ||
|
|
b87f200767 | ||
|
|
73ec8b2ffb | ||
|
|
107537999f | ||
|
|
e1bccbcc93 | ||
|
|
54e256be09 | ||
|
|
b26d510294 | ||
|
|
623ca5c675 | ||
|
|
76e00ae019 | ||
|
|
ff0ef12ecc | ||
|
|
1bcb2e2af6 | ||
|
|
796dc2ca41 | ||
|
|
80bf41254b | ||
|
|
bbc38d2f09 | ||
|
|
775f625f6e | ||
|
|
2203933fac | ||
|
|
907e32a731 | ||
|
|
d6919e340b | ||
|
|
e05ae42fbb | ||
|
|
e99f39d139 | ||
|
|
a4e7d6680d | ||
|
|
77ad14b359 | ||
|
|
9b3916d43e | ||
|
|
29f1aaf0da | ||
|
|
73cef02a4f | ||
|
|
2cb1897922 | ||
|
|
d0af9f4b4f | ||
|
|
08e9c80037 | ||
|
|
a42dbcd452 | ||
|
|
d3102565e4 | ||
|
|
c0387298c5 | ||
|
|
9e1e593dc4 | ||
|
|
849aad6497 | ||
|
|
847d69b621 | ||
|
|
6ff754d125 | ||
|
|
ff3d4b1337 | ||
|
|
2d2d5cd505 | ||
|
|
c6e8df0a00 | ||
|
|
7df9b1e4b5 | ||
|
|
0b4bac62a7 | ||
|
|
82cb6e7590 | ||
|
|
46dac6d3b1 | ||
|
|
b0b0d27c1f | ||
|
|
b12d1184bc | ||
|
|
d6e0d9ac61 | ||
|
|
c19b9746c8 | ||
|
|
4ed6177aee | ||
|
|
5900de101c | ||
|
|
61b3324941 | ||
|
|
0e858c5967 | ||
|
|
8c968e92f4 | ||
|
|
bea8ff1beb | ||
|
|
85e0b0a96d | ||
|
|
1b266ba7ac | ||
|
|
1810dc8b07 | ||
|
|
06b9da39ed | ||
|
|
11373763fd | ||
|
|
b3224ce59b | ||
|
|
145b17de0f | ||
|
|
623c5f6f81 | ||
|
|
bcbe70d231 | ||
|
|
8b52b9d8fc | ||
|
|
4ecc66d081 | ||
|
|
10d58175f1 | ||
|
|
923f5f6173 | ||
|
|
9332e1e3ff | ||
|
|
77f31fa127 | ||
|
|
4fd43657dc | ||
|
|
d9a24448e8 | ||
|
|
69ff78b656 | ||
|
|
0794b5c955 | ||
|
|
5c986cc68d | ||
|
|
688c002822 | ||
|
|
ced49d5314 | ||
|
|
3653baad9b | ||
|
|
b4aaf9712f | ||
|
|
e3274d7c95 | ||
|
|
a7a3d53dbd | ||
|
|
f8d776106f | ||
|
|
e34f648455 | ||
|
|
f4e2d05f25 | ||
|
|
bbc17b8995 | ||
|
|
a66bc13f4f | ||
|
|
7858efbd2b | ||
|
|
7da5e2c0c2 | ||
|
|
177c550a0c | ||
|
|
882725cabf | ||
|
|
3e16586d7a | ||
|
|
56a1838315 | ||
|
|
2d76cc5cfb | ||
|
|
192880e416 | ||
|
|
d1dac8aeca | ||
|
|
f13093249d | ||
|
|
99c602e3c7 | ||
|
|
040755bec9 | ||
|
|
1063680774 | ||
|
|
1f8644bb54 | ||
|
|
1d7c4b2615 | ||
|
|
d8ffd00fa6 | ||
|
|
f431c0fd89 | ||
|
|
571a1c1efe | ||
|
|
547afaa18f | ||
|
|
7789c39fe3 | ||
|
|
bfb2e8bb7e | ||
|
|
189290e093 | ||
|
|
7b541f3567 | ||
|
|
5cd16a62d9 | ||
|
|
89beb43ea2 | ||
|
|
55757857bd | ||
|
|
584444c1fc | ||
|
|
fb61bb4a63 | ||
|
|
ea8fd838d9 | ||
|
|
a3c58b0068 | ||
|
|
e0e8879caf | ||
|
|
feab19913b | ||
|
|
afbe56af0d | ||
|
|
d22ab8202c | ||
|
|
f4b6db222c | ||
|
|
44177c0d1b | ||
|
|
1d59ea3bd0 | ||
|
|
5e9ae64b2b | ||
|
|
46392d9dfc | ||
|
|
6734da4bee | ||
|
|
27edc963ce |
@@ -1,10 +0,0 @@
|
||||
FROM mcr.microsoft.com/devcontainers/typescript-node:24
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
EXPOSE 3000
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "Reactive Resume",
|
||||
"service": "reactive_resume",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"workspaceFolder": "/workspace",
|
||||
|
||||
"forwardPorts": [3000, 4000, 5432, 8333],
|
||||
"portsAttributes": {
|
||||
"3000": { "label": "Reactive Resume", "onAutoForward": "openBrowser" },
|
||||
"4000": { "label": "Browserless (Printer)" },
|
||||
"5432": { "label": "PostgreSQL" },
|
||||
"8333": { "label": "SeaweedFS (S3)" }
|
||||
},
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["biomejs.biome", "bradlc.vscode-tailwindcss", "lokalise.i18n-ally"],
|
||||
"settings": {
|
||||
"biome.enabled": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.biome": "explicit",
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"postCreateCommand": "corepack enable && pnpm install"
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
services:
|
||||
reactive_resume:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
browserless:
|
||||
condition: service_started
|
||||
seaweedfs:
|
||||
condition: service_healthy
|
||||
seaweedfs_create_bucket:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
TZ: Etc/UTC
|
||||
APP_URL: http://localhost:3000
|
||||
PRINTER_APP_URL: http://reactive_resume:3000
|
||||
PRINTER_ENDPOINT: ws://browserless:3000?token=1234567890
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
||||
AUTH_SECRET: change-me-to-a-secure-secret-key-in-production
|
||||
S3_ACCESS_KEY_ID: seaweedfs
|
||||
S3_SECRET_ACCESS_KEY: seaweedfs
|
||||
S3_REGION: us-east-1
|
||||
S3_ENDPOINT: http://seaweedfs:8333
|
||||
S3_BUCKET: reactive-resume
|
||||
S3_FORCE_PATH_STYLE: "true"
|
||||
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"]
|
||||
start_period: 10s
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
browserless:
|
||||
image: ghcr.io/browserless/chromium:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
QUEUED: "10"
|
||||
HEALTH: "true"
|
||||
CONCURRENT: "5"
|
||||
TOKEN: "1234567890"
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "curl", "-f", "http://localhost:3000/pressure?token=1234567890"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
seaweedfs:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
restart: unless-stopped
|
||||
command: server -s3 -filer -dir=/data -ip=0.0.0.0
|
||||
environment:
|
||||
AWS_ACCESS_KEY_ID: seaweedfs
|
||||
AWS_SECRET_ACCESS_KEY: seaweedfs
|
||||
volumes:
|
||||
- seaweedfs_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8888"]
|
||||
start_period: 10s
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
seaweedfs_create_bucket:
|
||||
image: quay.io/minio/mc:latest
|
||||
restart: on-failure
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
until mc alias set seaweedfs http://seaweedfs:8333 seaweedfs seaweedfs; do
|
||||
echo 'Waiting for SeaweedFS...';
|
||||
sleep 2;
|
||||
done;
|
||||
mc mb seaweedfs/reactive-resume --ignore-existing;
|
||||
"
|
||||
depends_on:
|
||||
seaweedfs:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
seaweedfs_data:
|
||||
@@ -1,15 +1,31 @@
|
||||
dist
|
||||
docs
|
||||
# VCS and editor state
|
||||
.git
|
||||
.gitignore
|
||||
.cursor
|
||||
.DS_Store
|
||||
|
||||
# Local configuration and runtime state
|
||||
.env*
|
||||
/data
|
||||
|
||||
# Dependency and package-manager caches
|
||||
node_modules
|
||||
**/node_modules
|
||||
.pnpm-store
|
||||
|
||||
# Build, test, and framework output
|
||||
dist
|
||||
**/dist
|
||||
.nitro
|
||||
.cursor
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
coverage
|
||||
.DS_Store
|
||||
.tanstack
|
||||
node_modules
|
||||
.turbo
|
||||
**/.turbo
|
||||
coverage
|
||||
reports
|
||||
|
||||
# Generated service-worker assets are rebuilt during the web build.
|
||||
public/sw.js
|
||||
public/workbox-*.js
|
||||
public/workbox-*.js
|
||||
|
||||
@@ -1,41 +1,60 @@
|
||||
# --- Server ---
|
||||
TZ="Etc/UTC"
|
||||
# --- Application ---
|
||||
# Port used by the web server in local development and self-hosted containers.
|
||||
PORT="3000"
|
||||
|
||||
# Public URL where the app is served. Used for auth callbacks, OAuth issuer URLs,
|
||||
# OpenGraph metadata, and absolute upload URLs.
|
||||
APP_URL="http://localhost:3000"
|
||||
|
||||
# Optional, uses APP_URL by default
|
||||
# PLEASE READ: This should be set to an internal URL (like http://host.docker.internal:3000 or http://{docker_service}:3000)
|
||||
# to let the browser navigate to a non-public instance of Reactive Resume.
|
||||
# This is required when the printer service is running inside Docker, and cannot reach the app via the APP URL,
|
||||
# which is usually when the APP_URL is localhost or a local network IP/hostname.
|
||||
PRINTER_APP_URL="http://host.docker.internal:3000"
|
||||
|
||||
# --- Printer ---
|
||||
PRINTER_ENDPOINT="ws://localhost:4000?token=1234567890"
|
||||
|
||||
# --- Database (PostgreSQL) ---
|
||||
# PostgreSQL connection URL. In Docker Compose, the hostname is usually `postgres`;
|
||||
# when running directly on your machine, `localhost` is typical.
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
|
||||
# --- Authentication ---
|
||||
# Generated using `openssl rand -hex 32`
|
||||
AUTH_SECRET="change-me-to-a-secure-secret-key-in-production"
|
||||
|
||||
# Better Auth Dashboard (optional)
|
||||
# Enables the Better Auth Dashboard plugin when set, you probably don't need this.
|
||||
BETTER_AUTH_API_KEY=""
|
||||
|
||||
# Social Auth (Google, optional)
|
||||
# Set both values to enable Google sign-in.
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
# Social Auth (GitHub, optional)
|
||||
# Set both values to enable GitHub sign-in.
|
||||
GITHUB_CLIENT_ID=""
|
||||
GITHUB_CLIENT_SECRET=""
|
||||
|
||||
# Custom OAuth Provider
|
||||
# Social Auth (LinkedIn, optional)
|
||||
# Set both values to enable LinkedIn sign-in.
|
||||
LINKEDIN_CLIENT_ID=""
|
||||
LINKEDIN_CLIENT_SECRET=""
|
||||
|
||||
# Custom OAuth Provider (optional)
|
||||
# Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET plus either OAUTH_DISCOVERY_URL or
|
||||
# the three manual endpoint URLs below.
|
||||
OAUTH_PROVIDER_NAME=""
|
||||
OAUTH_CLIENT_ID=""
|
||||
OAUTH_CLIENT_SECRET=""
|
||||
OAUTH_DISCOVERY_URL=""
|
||||
OAUTH_AUTHORIZATION_URL=""
|
||||
OAUTH_TOKEN_URL=""
|
||||
OAUTH_USER_INFO_URL=""
|
||||
|
||||
# Space-separated scopes requested from the custom OAuth provider.
|
||||
OAUTH_SCOPES="openid profile email"
|
||||
|
||||
# Comma-separated extra hosts/origins allowed for dynamic OAuth client redirect URIs.
|
||||
# By default, only the APP_URL origin is allowed.
|
||||
OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS=""
|
||||
|
||||
# --- Email (optional) ---
|
||||
# If all keys are disabled, the app logs the email to be sent to the console instead.
|
||||
# If SMTP_HOST, SMTP_USER, SMTP_PASS, or SMTP_FROM is missing, the app logs the
|
||||
# email to the console instead.
|
||||
SMTP_HOST="localhost"
|
||||
SMTP_PORT="1025"
|
||||
SMTP_USER=""
|
||||
@@ -44,7 +63,7 @@ SMTP_FROM="Reactive Resume <noreply@rxresu.me>"
|
||||
SMTP_SECURE="false"
|
||||
|
||||
# --- Storage (optional) ---
|
||||
# If all keys are disabled, the app uses local filesystem (/data) to store uploads instead.
|
||||
# If all S3 keys are disabled, the app uses local filesystem storage instead.
|
||||
# Make sure to mount this directory to a volume or the host filesystem to ensure data integrity.
|
||||
|
||||
# Seaweedfs
|
||||
@@ -56,14 +75,11 @@ S3_BUCKET="reactive-resume"
|
||||
S3_FORCE_PATH_STYLE="true"
|
||||
|
||||
# --- Feature Flags ---
|
||||
# This flag bypasses the check that the endpoint to fetch data for the printing of PDFs `getByIdForPrinter`, is only accessible from the server.
|
||||
# Useful for when you want to debug the /printer/{resumeId} route to quickly take a peek at the page that is sent to the printer.
|
||||
FLAG_DEBUG_PRINTER="false"
|
||||
|
||||
# This flag disables new signups, both on the web app and the server.
|
||||
FLAG_DISABLE_SIGNUPS="false"
|
||||
|
||||
# This flag disables email/password login. Disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true.
|
||||
# This flag disables email/password login. Disables email verification, forgot password, and reset password flows.
|
||||
# Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true.
|
||||
FLAG_DISABLE_EMAIL_AUTH="false"
|
||||
|
||||
# This flag disables the image processing.
|
||||
@@ -72,6 +88,11 @@ FLAG_DISABLE_IMAGE_PROCESSING="false"
|
||||
|
||||
# --- Others ---
|
||||
# Google Cloud API Key (optional)
|
||||
# This is not used within Reactive Resume, but in src/scripts/fonts/generate.ts to generate a list of fonts served by Google Fonts.
|
||||
# Note: Make sure "Google Fonts Developer API" is unrestricted.
|
||||
# For font-list generation tooling.
|
||||
# Requires "Google Fonts Developer API" to be enabled.
|
||||
GOOGLE_CLOUD_API_KEY=""
|
||||
|
||||
# Crowdin (optional)
|
||||
# For translation tooling.
|
||||
CROWDIN_PROJECT_ID=""
|
||||
CROWDIN_API_TOKEN=""
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
locales/*.po linguist-generated=true
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "individual",
|
||||
"role": "maintainer",
|
||||
"name": "Amruth Pillai",
|
||||
"email": "im.amruth@gmail.com",
|
||||
"email": "hello@amruthpillai.com",
|
||||
"description": "Software Engineer",
|
||||
"webpageUrl": {
|
||||
"url": "https://rxresu.me/funding.json"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
github: AmruthPillai
|
||||
open_collective: reactive-resume
|
||||
open_collective: reactive-resume
|
||||
|
||||
@@ -20,4 +20,4 @@ body:
|
||||
label: Feature Description
|
||||
description: A detailed description of the feature you would like to see in Reactive Resume. Please provide as much detail as possible as it will help me implement the feature faster.
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -1 +1 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: false
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: autofix.ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Remove Unused Dependencies
|
||||
run: pnpm knip --fix
|
||||
|
||||
- name: Lint and Format
|
||||
run: pnpm check
|
||||
|
||||
- name: Autofix
|
||||
uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Sync Translations from Crowdin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Sync Translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
download_translations: true
|
||||
localization_branch_name: "l10n"
|
||||
commit_message: "[skip ci] chore(i18n): sync translations from crowdin"
|
||||
pull_request_title: "Sync Translations from Crowdin"
|
||||
pull_request_body: "This is an automated pull request to sync translations from [Crowdin](https://crowdin.com/project/reactive-resume)."
|
||||
pull_request_labels: "l10n"
|
||||
pull_request_reviewers: "amruthpillai"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -9,6 +9,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
IMAGE: ${{ github.repository }}
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -41,16 +42,16 @@ jobs:
|
||||
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -58,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ env.IMAGE }}
|
||||
@@ -68,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Build and Push by Digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
sbom: true
|
||||
@@ -88,7 +89,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ matrix.arch }}
|
||||
path: /tmp/digests/*
|
||||
@@ -118,23 +119,23 @@ jobs:
|
||||
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -146,13 +147,13 @@ jobs:
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
||||
|
||||
|
||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ env.IMAGE }}
|
||||
@@ -181,7 +182,7 @@ jobs:
|
||||
--annotation "index:org.opencontainers.image.version=${{ steps.version.outputs.version }}" \
|
||||
$(printf 'ghcr.io/${{ env.IMAGE }}@sha256:%s ' *) \
|
||||
$(printf 'docker.io/${{ env.IMAGE }}@sha256:%s ' *)
|
||||
|
||||
|
||||
# Get the digest of the multi-arch manifest
|
||||
GHCR_DIGEST=$(docker buildx imagetools inspect ghcr.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
DOCKER_DIGEST=$(docker buildx imagetools inspect docker.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
@@ -195,7 +196,7 @@ jobs:
|
||||
run: |
|
||||
# Sign GHCR image
|
||||
cosign sign --yes ghcr.io/${{ env.IMAGE }}@${{ steps.manifest.outputs.ghcr_digest }}
|
||||
|
||||
|
||||
# Sign Docker Hub image
|
||||
cosign sign --yes docker.io/${{ env.IMAGE }}@${{ steps.manifest.outputs.docker_digest }}
|
||||
|
||||
@@ -212,4 +213,4 @@ jobs:
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
script: |
|
||||
cd docker
|
||||
./manage_stack.sh up reactive_resume
|
||||
./manage_stack.sh up reactive_resume
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
dist
|
||||
.env*
|
||||
/data
|
||||
.nitro
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
# Build Outputs
|
||||
.output
|
||||
.vercel
|
||||
.cursor
|
||||
TODO.md
|
||||
coverage
|
||||
.netlify
|
||||
.DS_Store
|
||||
.tanstack
|
||||
node_modules
|
||||
.wrangler
|
||||
|
||||
# Environment Variables
|
||||
.env*
|
||||
!.env.example
|
||||
public/sw.js
|
||||
public/sw.js.map
|
||||
scripts/**/*.json
|
||||
public/workbox-*.js
|
||||
public/workbox-*.js.map
|
||||
|
||||
# IDEs and Editors
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
.idea
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Log Files
|
||||
logs
|
||||
*.log*
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
|
||||
# TanStack Start
|
||||
.tanstack
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
reports
|
||||
|
||||
# Cache
|
||||
tmp
|
||||
temp
|
||||
.cache
|
||||
|
||||
# AI
|
||||
.codex
|
||||
.claude
|
||||
.cursor
|
||||
docs/superpowers
|
||||
|
||||
# Local Storage Data
|
||||
/data
|
||||
/apps/web/data
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// @ts-check
|
||||
|
||||
const nextPackages = ["@monaco-editor/react"];
|
||||
|
||||
const betaPackages = ["vite", "drizzle-orm", "drizzle-kit"];
|
||||
const betaPackages = ["drizzle-orm", "drizzle-kit", "drizzle-zod"];
|
||||
|
||||
/** @type {import('npm-check-updates').RunOptions} */
|
||||
module.exports = {
|
||||
upgrade: true,
|
||||
workspaces: true,
|
||||
install: "always",
|
||||
packageManager: "pnpm",
|
||||
target: (packageName) => {
|
||||
if (nextPackages.includes(packageName)) return "@next";
|
||||
if (betaPackages.includes(packageName)) return "@beta";
|
||||
return "latest";
|
||||
},
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
if [ "$LEFTHOOK" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
call_lefthook()
|
||||
{
|
||||
if test -n "$LEFTHOOK_BIN"
|
||||
then
|
||||
"$LEFTHOOK_BIN" "$@"
|
||||
elif lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
lefthook "$@"
|
||||
elif /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
/Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook "$@"
|
||||
else
|
||||
dir="$(git rev-parse --show-toplevel)"
|
||||
osArch=$(uname | tr '[:upper:]' '[:lower:]')
|
||||
cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/')
|
||||
if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook"
|
||||
then
|
||||
"$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook"
|
||||
then
|
||||
"$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook"
|
||||
then
|
||||
"$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/lefthook/bin/index.js"
|
||||
then
|
||||
"$dir/node_modules/lefthook/bin/index.js" "$@"
|
||||
elif go tool lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
go tool lefthook "$@"
|
||||
elif bundle exec lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
bundle exec lefthook "$@"
|
||||
elif yarn lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
yarn lefthook "$@"
|
||||
elif pnpm lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
pnpm lefthook "$@"
|
||||
elif swift package lefthook >/dev/null 2>&1
|
||||
then
|
||||
swift package --build-path .build/lefthook --disable-sandbox lefthook "$@"
|
||||
elif command -v mint >/dev/null 2>&1
|
||||
then
|
||||
mint run csjones/lefthook-plugin "$@"
|
||||
elif uv run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
uv run lefthook "$@"
|
||||
elif mise exec -- lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
mise exec -- lefthook "$@"
|
||||
elif devbox run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
devbox run lefthook "$@"
|
||||
else
|
||||
echo "Can't find lefthook in PATH"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
call_lefthook run "commit-msg" "$@"
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
if [ "$LEFTHOOK" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
call_lefthook()
|
||||
{
|
||||
if test -n "$LEFTHOOK_BIN"
|
||||
then
|
||||
"$LEFTHOOK_BIN" "$@"
|
||||
elif lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
lefthook "$@"
|
||||
elif /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
/Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook "$@"
|
||||
else
|
||||
dir="$(git rev-parse --show-toplevel)"
|
||||
osArch=$(uname | tr '[:upper:]' '[:lower:]')
|
||||
cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/')
|
||||
if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook"
|
||||
then
|
||||
"$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook"
|
||||
then
|
||||
"$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook"
|
||||
then
|
||||
"$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@"
|
||||
elif test -f "$dir/node_modules/lefthook/bin/index.js"
|
||||
then
|
||||
"$dir/node_modules/lefthook/bin/index.js" "$@"
|
||||
elif go tool lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
go tool lefthook "$@"
|
||||
elif bundle exec lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
bundle exec lefthook "$@"
|
||||
elif yarn lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
yarn lefthook "$@"
|
||||
elif pnpm lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
pnpm lefthook "$@"
|
||||
elif swift package lefthook >/dev/null 2>&1
|
||||
then
|
||||
swift package --build-path .build/lefthook --disable-sandbox lefthook "$@"
|
||||
elif command -v mint >/dev/null 2>&1
|
||||
then
|
||||
mint run csjones/lefthook-plugin "$@"
|
||||
elif uv run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
uv run lefthook "$@"
|
||||
elif mise exec -- lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
mise exec -- lefthook "$@"
|
||||
elif devbox run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
devbox run lefthook "$@"
|
||||
else
|
||||
echo "Can't find lefthook in PATH"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
call_lefthook run "pre-commit" "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss", "lokalise.i18n-ally"]
|
||||
"recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss", "typescriptteam.native-preview"]
|
||||
}
|
||||
|
||||
@@ -1,32 +1,23 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"biome.enabled": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.biome": "explicit",
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"files.readonlyInclude": {
|
||||
"**/locales/**.po": true,
|
||||
"**/routeTree.gen.ts": true,
|
||||
"pnpm-lock.yaml": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/locales/**.po": true,
|
||||
"**/routeTree.gen.ts": true,
|
||||
"locales/**.po": true,
|
||||
"pnpm-lock.yaml": true
|
||||
},
|
||||
"i18n-ally.enabledParsers": ["po"],
|
||||
"i18n-ally.localesPaths": ["locales"],
|
||||
"i18n-ally.sourceLanguage": "en-US",
|
||||
"search.exclude": {
|
||||
"**/locales/**.po": true,
|
||||
"**/routeTree.gen.ts": true,
|
||||
"locales/**.po": true,
|
||||
"pnpm-lock.yaml": true
|
||||
},
|
||||
"tailwindCSS.classFunctions": ["cn", "cva"],
|
||||
@@ -36,5 +27,5 @@
|
||||
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
],
|
||||
"tailwindCSS.experimental.configFile": "src/styles/globals.css",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.experimental.useTsgo": true
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
### Overview
|
||||
|
||||
Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with TanStack Start (React 19, Vite, Nitro). It serves both frontend and API on port 3000.
|
||||
|
||||
### Infrastructure services
|
||||
|
||||
Before running the dev server, Docker must be running with at least PostgreSQL. Start services via `compose.dev.yml`:
|
||||
|
||||
```bash
|
||||
sudo dockerd &>/var/log/dockerd.log &
|
||||
sudo docker compose -f compose.dev.yml up -d postgres browserless
|
||||
```
|
||||
|
||||
- **PostgreSQL** (port 5432) — required. The app auto-runs Drizzle migrations on startup via a Nitro plugin.
|
||||
- **Browserless** (port 4000) — required for PDF export. Maps container port 3000 to host port 4000.
|
||||
|
||||
### Environment variables
|
||||
|
||||
Copy `.env.example` to `.env` if not present. Key notes for local dev:
|
||||
|
||||
- `APP_URL` — local dev server origin on port 3000.
|
||||
- `PRINTER_APP_URL` — must use the Docker bridge gateway IP (not localhost) so the Browserless container can reach the app on the host. Get the IP with: `sudo docker network inspect reactive_resume_default --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'`
|
||||
- `PRINTER_ENDPOINT` — websocket URL to Browserless on host port 4000 with token `1234567890`.
|
||||
- `DATABASE_URL` — PostgreSQL connection using `postgres:postgres` credentials on localhost:5432.
|
||||
- S3/Storage and SMTP vars can be left empty — the app falls back to local filesystem and console-logged emails.
|
||||
|
||||
### Common commands
|
||||
|
||||
See `scripts` in `package.json`. Key ones:
|
||||
|
||||
| Task | Command |
|
||||
|---|---|
|
||||
| Dev server | `pnpm dev` (port 3000) |
|
||||
| Lint (Biome) | `pnpm lint` |
|
||||
| Typecheck | `pnpm typecheck` |
|
||||
| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) |
|
||||
|
||||
### Gotchas
|
||||
|
||||
- The Docker daemon needs `fuse-overlayfs` storage driver and `iptables-legacy` in the cloud VM (nested container environment).
|
||||
- `pnpm.onlyBuiltDependencies` in `package.json` controls which packages are allowed to run install scripts — no interactive `pnpm approve-builds` needed.
|
||||
- Email verification is optional in dev — after signup, click "Continue" to skip.
|
||||
- Vite 8 is beta (`^8.0.0-beta.15`); Nitro uses a nightly build. Occasional upstream issues may occur.
|
||||
@@ -1,39 +1,44 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# ---------- Dependencies Layer ----------
|
||||
FROM node:24-slim AS dependencies
|
||||
ARG NODE_VERSION=24
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
RUN mkdir -p /tmp/dev /tmp/prod
|
||||
|
||||
COPY package.json pnpm-lock.yaml /tmp/dev/
|
||||
COPY package.json pnpm-lock.yaml /tmp/prod/
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
cd /tmp/dev && pnpm install --frozen-lockfile
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
cd /tmp/prod && pnpm install --frozen-lockfile --prod
|
||||
|
||||
# ---------- Builder Layer ----------
|
||||
FROM node:24-slim AS builder
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
FROM node:${NODE_VERSION}-slim AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=dependencies /tmp/dev/node_modules ./node_modules
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
PNPM_HOME="/pnpm" \
|
||||
PATH="/pnpm:$PATH" \
|
||||
TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS pruner
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm dlx turbo@2.9.9 prune web --docker
|
||||
|
||||
RUN pnpm run build
|
||||
FROM base AS builder
|
||||
COPY --from=pruner /app/out/json/ ./
|
||||
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# ---------- Runtime Layer ----------
|
||||
FROM node:24-slim AS runtime
|
||||
COPY --from=pruner /app/out/full/ ./
|
||||
RUN pnpm turbo run build --filter=web
|
||||
|
||||
FROM base AS runtime-pruner
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm dlx turbo@2.9.9 prune @reactive-resume/runtime-externals --docker
|
||||
|
||||
FROM base AS runtime-deps
|
||||
COPY --from=runtime-pruner /app/out/json/ ./
|
||||
COPY --from=runtime-pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm --filter=@reactive-resume/runtime-externals deploy --prod --legacy /runtime-deps
|
||||
|
||||
FROM node:${NODE_VERSION}-slim AS runtime
|
||||
|
||||
LABEL maintainer="amruthpillai"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
@@ -44,20 +49,23 @@ LABEL org.opencontainers.image.url="https://rxresu.me"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.rxresu.me"
|
||||
LABEL org.opencontainers.image.source="https://github.com/amruthpillai/reactive-resume"
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ENV NODE_ENV="production" \
|
||||
PORT=3000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN mkdir -p /app/apps/web /app/data && chown node:node /app/data
|
||||
|
||||
COPY --from=builder /app/.output ./.output
|
||||
COPY --from=builder /app/migrations ./migrations
|
||||
COPY --from=dependencies /tmp/prod/node_modules ./node_modules
|
||||
COPY --from=runtime-deps --chown=node:node /runtime-deps/node_modules ./node_modules
|
||||
COPY --from=builder --chown=node:node /app/apps/web/.output ./apps/web/.output
|
||||
COPY --from=pruner --chown=node:node /app/migrations ./migrations
|
||||
|
||||
WORKDIR /app/apps/web
|
||||
|
||||
USER node
|
||||
|
||||
EXPOSE 3000/tcp
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
CMD ["node", "-e", "fetch('http://127.0.0.1:3000/api/health').then((r) => { if (!r.ok) process.exit(1); }).catch(() => process.exit(1));"]
|
||||
|
||||
ENTRYPOINT ["node", ".output/server/index.mjs"]
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<a href="https://rxresu.me">
|
||||
<img src="public/opengraph/banner.jpg" alt="Reactive Resume" />
|
||||
<img src="apps/web/public/opengraph/banner.jpg" alt="Reactive Resume" />
|
||||
</a>
|
||||
|
||||
<h1>Reactive Resume</h1>
|
||||
@@ -14,7 +14,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/github/package-json/v/amruthpillai/reactive-resume?style=flat-square" alt="Reactive Resume version">
|
||||
<img src="https://img.shields.io/github/package-json/v/amruthpillai/reactive-resume?style=flat-square" alt="Reactive Resume Version">
|
||||
<img src="https://img.shields.io/github/stars/amruthpillai/Reactive-Resume?style=flat-square" alt="GitHub Stars">
|
||||
<img src="https://img.shields.io/github/license/amruthpillai/Reactive-Resume?style=flat-square" alt="License" />
|
||||
<img src="https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square" alt="Docker Pulls" />
|
||||
@@ -69,63 +69,67 @@ Built with privacy as a core principle, Reactive Resume gives you complete owner
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/azurill.jpg" alt="Azurill" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/azurill.jpg" alt="Azurill" width="150" />
|
||||
<br /><sub><b>Azurill</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/bronzor.jpg" alt="Bronzor" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/bronzor.jpg" alt="Bronzor" width="150" />
|
||||
<br /><sub><b>Bronzor</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/chikorita.jpg" alt="Chikorita" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/chikorita.jpg" alt="Chikorita" width="150" />
|
||||
<br /><sub><b>Chikorita</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/ditto.jpg" alt="Ditto" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/ditto.jpg" alt="Ditto" width="150" />
|
||||
<br /><sub><b>Ditto</b></sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/gengar.jpg" alt="Gengar" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/gengar.jpg" alt="Gengar" width="150" />
|
||||
<br /><sub><b>Gengar</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/glalie.jpg" alt="Glalie" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/glalie.jpg" alt="Glalie" width="150" />
|
||||
<br /><sub><b>Glalie</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/kakuna.jpg" alt="Kakuna" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/kakuna.jpg" alt="Kakuna" width="150" />
|
||||
<br /><sub><b>Kakuna</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/lapras.jpg" alt="Lapras" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/lapras.jpg" alt="Lapras" width="150" />
|
||||
<br /><sub><b>Lapras</b></sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/leafish.jpg" alt="Leafish" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/leafish.jpg" alt="Leafish" width="150" />
|
||||
<br /><sub><b>Leafish</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/onyx.jpg" alt="Onyx" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/onyx.jpg" alt="Onyx" width="150" />
|
||||
<br /><sub><b>Onyx</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/pikachu.jpg" alt="Pikachu" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/pikachu.jpg" alt="Pikachu" width="150" />
|
||||
<br /><sub><b>Pikachu</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/rhyhorn.jpg" alt="Rhyhorn" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/rhyhorn.jpg" alt="Rhyhorn" width="150" />
|
||||
<br /><sub><b>Rhyhorn</b></sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="public/templates/jpg/ditgar.jpg" alt="Ditgar" width="150" />
|
||||
<img src="apps/web/public/templates/jpg/ditgar.jpg" alt="Ditgar" width="150" />
|
||||
<br /><sub><b>Ditgar</b></sub>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="apps/web/public/templates/jpg/meowth.jpg" alt="Meowth" width="150" />
|
||||
<br /><sub><b>Meowth</b></sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -151,38 +155,39 @@ For detailed setup instructions, environment configuration, and self-hosting gui
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Category | Technology |
|
||||
| ---------------- | ------------------------------------ |
|
||||
| Framework | TanStack Start (React 19, Vite) |
|
||||
| Runtime | Node.js |
|
||||
| Language | TypeScript |
|
||||
| Database | PostgreSQL with Drizzle ORM |
|
||||
| API | ORPC (Type-safe RPC) |
|
||||
| Auth | Better Auth |
|
||||
| Styling | Tailwind CSS |
|
||||
| UI Components | Radix UI |
|
||||
| State Management | Zustand + TanStack Query |
|
||||
| Category | Technology |
|
||||
| ---------------- | ------------------------------- |
|
||||
| Framework | TanStack Start (React 19, Vite) |
|
||||
| Runtime | Node.js |
|
||||
| Language | TypeScript |
|
||||
| Database | PostgreSQL with Drizzle ORM |
|
||||
| API | ORPC (Type-safe RPC) |
|
||||
| Auth | Better Auth |
|
||||
| Styling | Tailwind CSS |
|
||||
| UI Components | Radix UI |
|
||||
| State Management | Zustand + TanStack Query |
|
||||
|
||||
## Documentation
|
||||
|
||||
Comprehensive guides are available at [docs.rxresu.me](https://docs.rxresu.me):
|
||||
|
||||
| Guide | Description |
|
||||
| --------------------------------------------------------------------------- | --------------------------------- |
|
||||
| [Getting Started](https://docs.rxresu.me/getting-started) | First-time setup and basic usage |
|
||||
| [Self-Hosting](https://docs.rxresu.me/self-hosting/docker) | Deploy on your own server |
|
||||
| [Development Setup](https://docs.rxresu.me/contributing/development) | Local development environment |
|
||||
| [Project Architecture](https://docs.rxresu.me/contributing/architecture) | Codebase structure and patterns |
|
||||
| [Exporting Your Resume](https://docs.rxresu.me/guides/exporting-your-resume)| PDF and JSON export options |
|
||||
| Guide | Description |
|
||||
| ---------------------------------------------------------------------------- | -------------------------------- |
|
||||
| [Getting Started](https://docs.rxresu.me/getting-started) | First-time setup and basic usage |
|
||||
| [Self-Hosting](https://docs.rxresu.me/self-hosting/docker) | Deploy on your own server |
|
||||
| [Development Setup](https://docs.rxresu.me/contributing/development) | Local development environment |
|
||||
| [Project Architecture](https://docs.rxresu.me/contributing/architecture) | Codebase structure and patterns |
|
||||
| [Exporting Your Resume](https://docs.rxresu.me/guides/exporting-your-resume) | PDF and JSON export options |
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
Reactive Resume can be self-hosted using Docker. The stack includes:
|
||||
|
||||
- **PostgreSQL** — Database for storing user data and resumes
|
||||
- **Printer** — Headless Chromium service for PDF and screenshot generation
|
||||
- **SeaweedFS** (optional) — S3-compatible storage for file uploads
|
||||
|
||||
> **From v5.1.0 onwards** — PDF generation now runs entirely client-side via `@react-pdf/renderer`. New deployments no longer require Browserless, Chromium, or any external print service as a dependency. The `PRINTER_*` and `BROWSERLESS_*` environment variables are no longer read and can be removed from your `.env`.
|
||||
|
||||
Pull the latest image from Docker Hub or GitHub Container Registry:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Reactive Resume provides security updates for version `5.0.x` and newer.
|
||||
Earlier major versions are no longer supported.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| >= 5.0 | :white_check_mark: |
|
||||
| 4.x | :x: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability, please report it
|
||||
privately instead of opening a public issue.
|
||||
|
||||
Preferred reporting channels:
|
||||
|
||||
- Use GitHub's private vulnerability reporting for this repository.
|
||||
- If that is unavailable, email `hello@amruthpillai.com` with the subject
|
||||
`Security Vulnerability: Reactive Resume`.
|
||||
|
||||
Please include as much detail as possible, including:
|
||||
|
||||
- A description of the vulnerability and its impact.
|
||||
- Steps to reproduce or a proof of concept.
|
||||
- Affected versions, configuration, or deployment details.
|
||||
- Any relevant logs, screenshots, or links.
|
||||
|
||||
You can expect an initial response within 7 days. If the report is accepted,
|
||||
we will coordinate remediation and disclosure timing with you before publishing
|
||||
details publicly.
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "base-nova",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "phosphor",
|
||||
"aliases": {
|
||||
"components": "@reactive-resume/ui/components",
|
||||
"utils": "@reactive-resume/utils/style",
|
||||
"hooks": "@reactive-resume/ui/hooks",
|
||||
"lib": "@reactive-resume/utils",
|
||||
"ui": "@reactive-resume/ui/components"
|
||||
},
|
||||
"rtl": true,
|
||||
"menuColor": "default-translucent",
|
||||
"menuAccent": "subtle",
|
||||
"registries": {}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineConfig } from "@lingui/cli";
|
||||
import { formatter } from "@lingui/format-po";
|
||||
|
||||
export default defineConfig({
|
||||
sourceLocale: "en-US",
|
||||
@@ -16,6 +17,7 @@ export default defineConfig({
|
||||
"de-DE",
|
||||
"el-GR",
|
||||
"en-US",
|
||||
"en-GB",
|
||||
"es-ES",
|
||||
"fa-IR",
|
||||
"fi-FI",
|
||||
@@ -44,6 +46,7 @@ export default defineConfig({
|
||||
"ro-RO",
|
||||
"ru-RU",
|
||||
"sk-SK",
|
||||
"sl-SI",
|
||||
"sq-AL",
|
||||
"sr-SP",
|
||||
"sv-SE",
|
||||
@@ -62,9 +65,9 @@ export default defineConfig({
|
||||
"zu-ZA": "en-US",
|
||||
default: "en-US",
|
||||
},
|
||||
formatOptions: {
|
||||
format: formatter({
|
||||
lineNumbers: false,
|
||||
},
|
||||
}),
|
||||
catalogs: [
|
||||
{
|
||||
path: "<rootDir>/locales/{locale}",
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite dev",
|
||||
"serve": "vite preview",
|
||||
"start": "node .output/server/index.mjs",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:coverage": "vitest run --coverage --passWithNoTests",
|
||||
"test:ci": "vitest run --coverage --reporter=default --reporter=github-actions --reporter=json --reporter=junit --outputFile.json=reports/vitest-results.json --outputFile.junit=reports/vitest-junit.xml --passWithNoTests",
|
||||
"test:agent": "vitest run --reporter=agent --reporter=json --outputFile.json=reports/vitest-results.json --passWithNoTests",
|
||||
"lingui:extract": "lingui extract --clean --overwrite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.4.1",
|
||||
"@better-auth/api-key": "^1.6.9",
|
||||
"@better-auth/infra": "^0.2.6",
|
||||
"@better-auth/oauth-provider": "^1.6.9",
|
||||
"@better-auth/passkey": "^1.6.9",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@lingui/core": "^6.0.1",
|
||||
"@lingui/react": "^6.0.1",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"@orpc/client": "^1.14.2",
|
||||
"@orpc/json-schema": "^1.14.2",
|
||||
"@orpc/openapi": "^1.14.2",
|
||||
"@orpc/server": "^1.14.2",
|
||||
"@orpc/tanstack-query": "^1.14.2",
|
||||
"@orpc/zod": "^1.14.2",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@react-pdf/renderer": "^4.5.1",
|
||||
"@reactive-resume/ai": "workspace:*",
|
||||
"@reactive-resume/api": "workspace:*",
|
||||
"@reactive-resume/auth": "workspace:*",
|
||||
"@reactive-resume/db": "workspace:*",
|
||||
"@reactive-resume/env": "workspace:*",
|
||||
"@reactive-resume/fonts": "workspace:*",
|
||||
"@reactive-resume/import": "workspace:*",
|
||||
"@reactive-resume/pdf": "workspace:*",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@reactive-resume/ui": "workspace:*",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@tanstack/react-form": "^1.29.1",
|
||||
"@tanstack/react-hotkeys": "^0.10.0",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"@tanstack/react-router": "^1.169.2",
|
||||
"@tanstack/react-router-ssr-query": "^1.166.12",
|
||||
"@tanstack/react-start": "^1.167.65",
|
||||
"@tiptap/extension-color": "^3.22.5",
|
||||
"@tiptap/extension-highlight": "^3.22.5",
|
||||
"@tiptap/extension-table": "^3.22.5",
|
||||
"@tiptap/extension-text-align": "^3.22.5",
|
||||
"@tiptap/extension-text-style": "^3.22.5",
|
||||
"@tiptap/pm": "^3.22.5",
|
||||
"@tiptap/react": "^3.22.5",
|
||||
"@tiptap/starter-kit": "^3.22.5",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@uiw/color-convert": "^2.10.1",
|
||||
"@uiw/react-color-colorful": "^2.10.1",
|
||||
"better-auth": "1.6.9",
|
||||
"cmdk": "^1.1.1",
|
||||
"drizzle-orm": "1.0.0-beta.22",
|
||||
"es-toolkit": "^1.46.1",
|
||||
"fuse.js": "^7.3.0",
|
||||
"immer": "^11.1.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
"motion": "^12.38.0",
|
||||
"pdfjs-dist": "5.7.284",
|
||||
"pg": "^8.20.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-resizable-panels": "^4.11.0",
|
||||
"react-window": "^2.2.7",
|
||||
"react-zoom-pan-pinch": "^4.0.3",
|
||||
"sonner": "^2.0.7",
|
||||
"srvx": "^0.11.15",
|
||||
"ts-pattern": "^5.9.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"zod": "^4.4.3",
|
||||
"zustand": "^5.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lingui/babel-plugin-lingui-macro": "^6.0.1",
|
||||
"@lingui/cli": "^6.0.1",
|
||||
"@lingui/format-po": "^6.0.1",
|
||||
"@lingui/vite-plugin": "^6.0.1",
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@types/pg": "^8.20.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"nitro": "3.0.260429-beta",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.11",
|
||||
"vite-plugin-pwa": "^1.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { definePlugin } from "nitro";
|
||||
import { Pool } from "pg";
|
||||
import { env } from "@reactive-resume/env/server";
|
||||
|
||||
function resolveMigrationsFolder(): string {
|
||||
let dir = import.meta.dirname;
|
||||
|
||||
while (dir !== path.dirname(dir)) {
|
||||
const candidate = path.join(dir, "migrations");
|
||||
if (existsSync(candidate)) return candidate;
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
|
||||
throw new Error(`Could not locate migrations folder relative to ${import.meta.dirname}`);
|
||||
}
|
||||
|
||||
const migrationsFolder = resolveMigrationsFolder();
|
||||
|
||||
export default definePlugin(async () => {
|
||||
console.info("Running database migrations...");
|
||||
|
||||
const connectionString = env.DATABASE_URL;
|
||||
|
||||
const pool = new Pool({ connectionString });
|
||||
const db = drizzle({ client: pool });
|
||||
|
||||
try {
|
||||
await migrate(db, { migrationsFolder });
|
||||
console.info("Database migrations completed");
|
||||
} catch (error) {
|
||||
console.error("Database migrations failed", { error });
|
||||
throw error;
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 958 B |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -5,7 +5,7 @@
|
||||
"type": "individual",
|
||||
"role": "maintainer",
|
||||
"name": "Amruth Pillai",
|
||||
"email": "im.amruth@gmail.com",
|
||||
"email": "hello@amruthpillai.com",
|
||||
"description": "Software Engineer",
|
||||
"webpageUrl": {
|
||||
"url": "https://rxresu.me/funding.json"
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "Reactive Resume",
|
||||
"short_name": "Reactive Resume",
|
||||
"description": "A free and open-source resume builder.",
|
||||
"id": "/?source=pwa",
|
||||
"start_url": "/?source=pwa",
|
||||
"scope": "/",
|
||||
"lang": "en",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#09090B",
|
||||
"background_color": "#09090B",
|
||||
"icons": [
|
||||
{ "src": "favicon.ico", "sizes": "128x128", "type": "image/x-icon" },
|
||||
{ "src": "pwa-64x64.png", "sizes": "64x64", "type": "image/png" },
|
||||
{ "src": "pwa-192x192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "pwa-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "maskable-icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "screenshots/web/1-landing-page.webp",
|
||||
"sizes": "1920x1080 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide",
|
||||
"label": "Landing Page"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/web/2-resume-dashboard.webp",
|
||||
"sizes": "1920x1080 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide",
|
||||
"label": "Resume Dashboard"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/web/3-builder-screen.webp",
|
||||
"sizes": "1920x1080 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide",
|
||||
"label": "Builder Screen"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/web/4-template-gallery.webp",
|
||||
"sizes": "1920x1080 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide",
|
||||
"label": "Template Gallery"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile/1-landing-page.webp",
|
||||
"sizes": "1284x2778 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "narrow",
|
||||
"label": "Landing Page"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile/2-resume-dashboard.webp",
|
||||
"sizes": "1284x2778 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "narrow",
|
||||
"label": "Resume Dashboard"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile/3-builder-screen.webp",
|
||||
"sizes": "1284x2778 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "narrow",
|
||||
"label": "Builder Screen"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile/4-template-gallery.webp",
|
||||
"sizes": "1284x2778 any",
|
||||
"type": "image/webp",
|
||||
"form_factor": "narrow",
|
||||
"label": "Template Gallery"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
"ai",
|
||||
"builder",
|
||||
"business",
|
||||
"career",
|
||||
"cv",
|
||||
"editor",
|
||||
"free",
|
||||
"generator",
|
||||
"job-search",
|
||||
"multilingual",
|
||||
"open-source",
|
||||
"privacy",
|
||||
"productivity",
|
||||
"resume",
|
||||
"self-hosted",
|
||||
"templates",
|
||||
"utilities",
|
||||
"writing"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 385 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |