Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
ac66b87
chore: add docker-based local dev environment
liguobao Apr 16, 2026
423f47e
chore: migrate babel 6 to babel 7 (node 22 upgrade phase 1)
liguobao Apr 16, 2026
4d2e042
chore: replace better-npm-run with cross-env (node 22 upgrade phase 2)
liguobao Apr 16, 2026
e21fdea
chore: replace phantomjs with puppeteer-core (node 22 upgrade phase 3)
liguobao Apr 16, 2026
ae0853d
chore: migrate webpack pipeline to webpack 5 for node 22
liguobao Apr 16, 2026
1575f10
chore: remove stray macos metadata
liguobao Apr 16, 2026
15eb6a5
fix: share dashboard history instance
liguobao Apr 16, 2026
0bcd57f
chore: reduce local development noise
liguobao Apr 16, 2026
f6043b2
fix: remove deprecated viewport density hint
liguobao Apr 17, 2026
47254f9
chore: replace deprecated request with native fetch (node 22 upgrade …
liguobao Apr 17, 2026
470080d
fix: hot-reload webpack assets manifest by mtime
liguobao Apr 17, 2026
0f643f8
chore: upgrade log4js 1.x to 6.x (node 22 upgrade phase 6)
liguobao Apr 17, 2026
16424d5
chore: upgrade config/nodemon/husky (node 22 upgrade phase 7)
liguobao Apr 17, 2026
0e5ee75
chore: upgrade runtime and react stack
liguobao Apr 17, 2026
d253e37
chore: upgrade koa base dependencies
liguobao Apr 17, 2026
eb49ba0
chore: upgrade shared utility dependencies
liguobao Apr 17, 2026
6e951ae
chore: upgrade frontend utility packages
liguobao Apr 17, 2026
4bc1a94
chore: upgrade koa view middlewares
liguobao Apr 17, 2026
d9303bd
chore: upgrade koa session middlewares
liguobao Apr 17, 2026
8425bbd
chore: upgrade frontend build tooling
liguobao Apr 17, 2026
319276b
chore: upgrade koa router
liguobao Apr 17, 2026
477e2f4
chore: upgrade react image
liguobao Apr 17, 2026
3d154e1
fix: adapt react image v4 imports
liguobao Apr 18, 2026
fe1982e
chore: replace react async component
liguobao Apr 18, 2026
571e828
fix: replace dashboard system imports
liguobao Apr 18, 2026
f716235
chore: upgrade rc-times
liguobao Apr 18, 2026
c880412
chore: upgrade react cropper
liguobao Apr 18, 2026
c452bc5
chore: upgrade babel loader
liguobao Apr 18, 2026
e21720b
chore: upgrade postcss preset env
liguobao Apr 18, 2026
f06fb71
chore: upgrade babel loader to v10
liguobao Apr 18, 2026
a963d08
chore: upgrade eslint import resolver webpack
liguobao Apr 18, 2026
f9eec88
chore: upgrade sentry browser to v10
liguobao Apr 18, 2026
da26c2c
chore: upgrade validator and mq-utils
liguobao Apr 18, 2026
73294b7
chore: upgrade cache-manager to v7 and revert mq-utils
liguobao Apr 18, 2026
d137d90
chore: upgrade jquery to v4
liguobao Apr 18, 2026
d034458
chore: upgrade puppeteer core to v24
liguobao Apr 18, 2026
7e71444
fix: resolve jquery 4 esm default via providePlugin
liguobao Apr 18, 2026
12d2323
chore: upgrade react and react-dom to v19
liguobao Apr 18, 2026
3ec2c54
chore: upgrade eslint stack to 8.57 + airbnb 19
liguobao Apr 18, 2026
ca27a61
chore: upgrade koa 2 -> 3
liguobao Apr 18, 2026
95ce060
chore: upgrade to react-router 7 (drop react-router-config + history)
liguobao Apr 18, 2026
a53cb3b
feat: add localdev mock mode for standalone dashboard preview
liguobao Apr 18, 2026
1cf7b5d
chore: flesh out mock resume fixture for share page preview
liguobao Apr 18, 2026
f7d3c80
chore: align local resume mock with share layout
liguobao Apr 18, 2026
5391542
fix: preserve css module snake case exports
liguobao Apr 18, 2026
30b638c
fix: avoid empty locale switcher on mock resume page
liguobao Apr 19, 2026
5955707
chore: restore locale switcher on mock resume page
liguobao Apr 19, 2026
f0c767b
chore: flesh out mock hotmap and per-repo languages
liguobao Apr 19, 2026
4799ed2
fix: shim jquery 4 $.type for slick-carousel
liguobao Apr 19, 2026
37e4522
chore: migrate react-beautiful-dnd to @hello-pangea/dnd
liguobao Apr 19, 2026
e9ff5da
chore: upgrade chart.js to v4
liguobao Apr 19, 2026
bd6632e
chore: bump deps for v4/v12 line and drop rc-times
liguobao Apr 20, 2026
197f3a2
chore: migrate Hotmap to cal-heatmap v4
liguobao Apr 20, 2026
b17c063
chore: sync GitHub/Hotmap path to cal-heatmap v4 variant
liguobao Apr 20, 2026
ce266a6
chore: tweak highcharts stock config for v12
liguobao Apr 20, 2026
f6c7ce5
refactor: replace rc-times picker with native interval select
liguobao Apr 20, 2026
5f3a8c7
feat: support local mock mode for mq and oss
liguobao Apr 20, 2026
051dd79
fix: react 19 leftover cleanup
liguobao Apr 20, 2026
680e1c9
chore: finish Node 22 React 19 upgrade
liguobao May 20, 2026
512c2f3
feat: simplify to resume-only sqlite app
liguobao May 20, 2026
71e29e7
chore: clean up config and docs
liguobao May 20, 2026
6aff140
feat: 新增本地编辑
liguobao May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"presets": [
"env",
"stage-0",
"react"
["@babel/preset-env", { "loose": true }],
"@babel/preset-react"
],
"plugins": [
"transform-runtime",
"add-react-displayname"
["@babel/plugin-transform-class-properties", { "loose": true }],
["@babel/plugin-transform-private-methods", { "loose": true }],
["@babel/plugin-transform-private-property-in-object", { "loose": true }],
["@babel/plugin-transform-runtime", { "regenerator": true }]
]
}
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "hacknical-dev",
"runtimeExecutable": "/tmp/start-hacknical.sh",
"runtimeArgs": [],
"port": 4000
}
]
}
23 changes: 21 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false,
"babelOptions": {
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
},
"extends": "eslint-config-airbnb",
"plugins": [
"react"
Expand Down Expand Up @@ -48,7 +54,20 @@
"no-return-await": 0,
"no-continue": 0,
"no-return-assign": 0,
"no-restricted-syntax": 0
"no-restricted-syntax": 0,
"arrow-parens": 0,
"implicit-arrow-linebreak": 0,
"operator-linebreak": 0,
"no-multiple-empty-lines": 0,
"no-promise-executor-return": 0,
"no-else-return": 0,
"default-param-last": 0,
"prefer-regex-literals": 0,
"prefer-object-spread": 0,
"import/order": 0,
"import/no-import-module-exports": 0,
"import/no-relative-packages": 0,
"react/jsx-props-no-spreading": 0
},
"globals": {
"require": true,
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
/node_modules
/public/dll
/public/downloads
/public/assets
/data/*.sqlite
/data/*.sqlite-*
.idea
.vscode
/log
/app/config/webpack-assets.json
/app/config/webpack-assets.json
.DS_Store
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npm run lint
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
legacy-peer-deps=true
audit=false
fund=false
36 changes: 36 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Node 22 开发镜像,兼容现代工具链,也能在 Apple Silicon 上原生运行
FROM node:22-bookworm-slim

# node-gyp 需要 python3 和构建工具
# chromium 给 puppeteer-core 用(生成简历 PDF)
# fonts-noto-cjk 用于中文字符渲染
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
ca-certificates \
git \
chromium \
fonts-liberation \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 先拷贝依赖描述和补丁,用 docker 缓存层加速重建
COPY package.json package-lock.json .npmrc ./
COPY patches ./patches

# puppeteer-core 不下载内置 chromium,用 apt 装的那个
ENV NPM_CONFIG_LOGLEVEL=warn \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
PUPPETEER_SKIP_DOWNLOAD=true

# 用 lockfile 保证 Node 22/npm 10 下依赖可复现
RUN npm config set registry https://registry.npmmirror.com \
&& npm ci --unsafe-perm

# 源码后续通过 volume 挂载进来
EXPOSE 4000

CMD ["npm", "run", "start-dev"]
80 changes: 41 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,69 @@

![hacknical-logo-with-text](./doc/screenshots/logos/hacknical-logo-large.png)

> A website for GitHub user to generate his GitHub data analysis (contributions/commits/languages/repos datas), helps to make a better resume.
> A lightweight online resume editor and sharing service backed by local SQLite storage.

[中文版 README](./doc/README-ZH.md)

**Attention:Most of the pages support English now😁😁😁, including github data analysis page.**
## Features

Extract dependency:

- UI Components --> [light-ui](https://github.com/ecmadao/light-ui)
- GitHub API crawler --> [hacknical-github](https://github.com/ecmadao/hacknical-github)

## Examples

- [My GitHub data analysis](https://hacknical.com/ecmadao/github)
- Local username login, without third-party OAuth.
- Create, edit, share, and export online resumes.
- SQLite storage for users, resumes, share settings, and basic stats.
- Optional favicon lookup through `besticon`.
- Optional Ali OSS integration for image/PDF upload paths.

## Screenshots

> login page

![login page](./doc/screenshots/login-en.png)

> github datas analysis
## Local Development

![github datas](./doc/screenshots/github-en.png)
Requirements:

## About
- Node.js 22
- npm 10

[中文版说明](./doc/ABOUT-zh.md)
```bash
npm ci
npm run start-app
```

The app runs at `http://localhost:4000`. Local data is stored in `data/hacknical-local.sqlite`.

## Todos
Docker development is also available:

- [x] support English
- [x] support orgs
- [ ] support forked repos
- [ ] support edit resume in mobile
- [x] support show resume in mobile
- [x] support export resume to PDF
```bash
docker compose up -d --build
docker compose logs -f app
```

## Techs
The Docker setup starts the app plus `besticon`; it does not start Redis because the default cache and queue are in memory.

- backend
## Configuration

- koa2
- redis
- mongoose
- nunjucks
- request
- pm2
Runtime config lives in `config/default.json` and `config/localdev.json`. Environment overrides are declared in `config/custom-environment-variables.json`.

- frontend
Common overrides:

- react
- redux
- react-router
- particles
- scrollreveal
- chart.js
- clipboard
- headroom.js
- webpack
- `PORT`: server port; `config/localdev.json` defaults to `4000`.
- `HACKNICAL_DATABASE_CLIENT`: storage backend; defaults to `sqlite`.
- `HACKNICAL_SQLITE_PATH`: SQLite file path.
- `HACKNICAL_BESTICON_URL`: favicon service URL.
- `HACKNICAL_CACHE_SOURCE`: cache backend; defaults to `memory`.
- `HACKNICAL_REDIS_HOST` / `HACKNICAL_REDIS_PORT`: optional Redis cache connection when `HACKNICAL_CACHE_SOURCE=redis`.
- `HACKNICAL_ALI_ACCESS_ID` / `HACKNICAL_ALI_ACCESS_KEY`: Ali OSS credentials for upload features.

## Tech Stack

- backend: Node.js 22, Koa, SQLite, Nunjucks, Puppeteer, PM2
- frontend: React 19, Redux, React Router, Chart.js, Webpack 5

## About

[中文版说明](./doc/ABOUT-zh.md)

## License

Expand Down
31 changes: 28 additions & 3 deletions app/bin/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import staticServer from 'koa-static'

import router from '../routes'
import logger from '../utils/logger'
import network from '../services/network'
import mqMiddleware from '../middlewares/mq'
import errorMiddleware from '../middlewares/error'
import localeMiddleware from '../middlewares/locale'
Expand All @@ -26,7 +27,14 @@ import firewallMiddleware from '../middlewares/firewall'

// Handle unhandled promise rejections for Redis queue
process.on('unhandledRejection', (reason, promise) => {
if (reason && reason.message && reason.message.includes('queueExists')) {
const reasonText = [
reason && reason.name,
reason && reason.message,
typeof reason === 'string' ? reason : '',
reason && typeof reason.inspect === 'function' ? String(reason.inspect()) : ''
].filter(Boolean).join(' ')

if (reasonText.includes('queueExists')) {
logger.debug('Redis queue already exists, continuing...')
return
}
Expand All @@ -51,7 +59,7 @@ app.use(cors())
// bodyparser
app.use(bodyParser({
onerror: (err, ctx) => {
ctx.throw('body parse error', 422)
ctx.throw(422, 'body parse error')
}
}))

Expand Down Expand Up @@ -83,6 +91,23 @@ const CONFIG = {
}
app.use(session(CONFIG, app))

// mock session (localdev only) - auto-logs-in as demo user
if (config.has('mock') && config.get('mock')) {
app.use(async (ctx, next) => {
if (!ctx.session.githubLogin) {
const user = await network.user.createUser({
login: 'demo',
userName: 'demo'
})
ctx.session.githubLogin = 'demo'
ctx.session.userId = user.userId
ctx.session.githubToken = null
ctx.session.githubAvator = user.avator
}
await next()
})
}

// cache
app.use(redisMiddleware())
// mq
Expand All @@ -97,7 +122,7 @@ app.use(new Csrf())
app.use(async (ctx, next) => {
ctx.state = Object.assign({}, ctx.state, {
assetsPath: assetsMiddleware,
csrf: ctx.csrf,
csrf: ctx.state._csrf,
env: process.env.NODE_ENV,
footer: {
about: ctx.__('dashboard.about'),
Expand Down
19 changes: 10 additions & 9 deletions app/config/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"text": "En",
"id": "en"
},
"description": "hacknical - A website for GitHub user to make a better resume.",
"keywords": "GitHub, GitHub-Contributions, GitHub-Analysis, Resume, Resume-Template, Data-Analysis",
"description": "hacknical - Create, edit, share, and export online resumes.",
"keywords": "Resume, Online Resume, Resume Template, Resume Export",
"loginPage": {
"title": "hacknical | Make a better resume"
},
Expand All @@ -27,7 +27,7 @@
"code": "source code"
},
"sharePage": {
"title": "%s's GitHub summary | hacknical",
"title": "%s's share page | hacknical",
"joinAt": "Joined at"
},
"resumePage": {
Expand All @@ -38,7 +38,7 @@
"analysis": "Share records",
"menu": {
"shareDatas": "Share Records",
"githubShare": "GitHub Analysis",
"githubShare": "Resume Overview",
"resumeShare": "Personal Resume",
"dataRefresh": "Settings",
"about": "About",
Expand All @@ -55,6 +55,7 @@
"update": "Update failed",
"resume": "Can not find your resume",
"emptyResume": "Please create your resume",
"loginName": "Use a 2-39 character username with letters, numbers, underscore, or hyphen",
"invalidateEmail": "Invalidate email",
"logout": "Session is missed",
"download": "Download failed. Please retry later"
Expand All @@ -67,14 +68,14 @@
"updateOrgs": "User orgs are successfully update"
},
"share": {
"text": "Wanna check your GitHub data analysis visually?",
"mobileText": "Check your GitHub data analysis",
"text": "Check out my online resume.",
"mobileText": "Check my online resume",
"toggleOpen": "Share enabled",
"toggleClose": "Share disabled"
},
"resume": {
"linkGithub": "Resume linked with GitHub",
"unlinkGithub": "Resume unlinked with GitHub",
"linkGithub": "Resume attachment enabled",
"unlinkGithub": "Resume attachment disabled",
"template": "Resume template successfully changed",
"hireAvailable": "Hire status was successfully updated",
"simplifyUrl": "Simplify share url opened",
Expand All @@ -84,7 +85,7 @@
"succeed": "Data update succeed",
"pending": "Data is updating in the background",
"running": "Data is updating in the background",
"failed": "GitHub token expired, please re-login",
"failed": "Session expired, please re-login",
"frequently": "Update too frequently, please retry later"
}
}
Expand Down
Loading