chore: add VS Code workspace config and improve CI infrastructure

Add .vscode/ with Biome formatter, Tailwind, Vitest, Playwright, and
Python debug configs. Extract shared pnpm/Node setup into a composite
GitHub Action and add Dependabot and dependency-review workflows.
This commit is contained in:
SnapOtter
2026-05-01 00:28:10 +08:00
parent d12b1c0fc6
commit 710b580cee
11 changed files with 765 additions and 64 deletions
+15
View File
@@ -0,0 +1,15 @@
name: Setup Project
description: Install pnpm, Node.js 22, and dependencies
runs:
using: composite
steps:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
shell: bash
+52
View File
@@ -0,0 +1,52 @@
version: 2
updates:
# npm/pnpm dependencies (root lockfile covers entire monorepo)
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
groups:
production-deps:
dependency-type: "production"
update-types:
- "minor"
- "patch"
dev-deps:
dependency-type: "development"
update-types:
- "minor"
- "patch"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
# Python dependencies (AI sidecar)
- package-ecosystem: "pip"
directory: "/packages/ai/python"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
# Docker base images
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 3
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
groups:
actions:
patterns:
- "*"
+4 -35
View File
@@ -30,15 +30,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- run: pnpm lint
typecheck:
@@ -46,15 +38,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- run: pnpm typecheck
test:
@@ -66,14 +50,7 @@ jobs:
- name: Install system dependencies (HEIC + ExifTool)
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends libheif-examples libheif-plugin-x265 libheif-plugin-libde265 libimage-exiftool-perl
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- run: pnpm test:ci
pip-audit:
@@ -102,15 +79,7 @@ jobs:
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- run: pnpm build
# docker:
+18
View File
@@ -0,0 +1,18 @@
name: Dependency Review
on: [pull_request]
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
name: Review
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: always
+1 -9
View File
@@ -17,15 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- name: Build Docs
run: pnpm --filter @snapotter/docs docs:build
+1 -9
View File
@@ -17,15 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- name: Build Landing Page
run: pnpm --filter @snapotter/landing build
+1 -11
View File
@@ -22,17 +22,7 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: ./.github/actions/setup
- name: Run semantic-release
env:
+40
View File
@@ -0,0 +1,40 @@
{
"recommendations": [
// ── Core: Linting & Formatting ──
"biomejs.biome",
// ── Languages ──
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.debugpy",
// ── Frontend ──
"bradlc.vscode-tailwindcss",
// ── Testing ──
"vitest.explorer",
"ms-playwright.playwright",
// ── Infrastructure ──
"ms-azuretools.vscode-docker",
// ── GitHub ──
"GitHub.vscode-pull-request-github",
"GitHub.vscode-github-actions",
// ── DX Enhancements ──
"yoavbls.pretty-ts-errors",
"usernamehw.errorlens",
"eamodio.gitlens",
"vivaxy.vscode-conventional-commits",
"redhat.vscode-yaml",
"qwtel.sqlite-viewer",
"mikestead.dotenv",
"EditorConfig.EditorConfig"
],
"unwantedRecommendations": [
// Conflicts with Biome
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
}
+186
View File
@@ -0,0 +1,186 @@
{
"version": "0.2.0",
"configurations": [
// ══════════════════════════════════════════════════════════════════════
// Backend
// ══════════════════════════════════════════════════════════════════════
{
"name": "API: Fastify (tsx)",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"args": ["watch", "src/index.ts"],
"cwd": "${workspaceFolder}/apps/api",
"env": {
"PORT": "13490",
"NODE_ENV": "development"
},
"sourceMaps": true,
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
// ══════════════════════════════════════════════════════════════════════
// Frontend
// ══════════════════════════════════════════════════════════════════════
{
"name": "Web: Vite Dev Server",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/vite",
"args": ["--port", "1351"],
"cwd": "${workspaceFolder}/apps/web",
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Web: Debug in Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:1351",
"webRoot": "${workspaceFolder}/apps/web/src",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
"webpack:///src/*": "${workspaceFolder}/apps/web/src/*"
}
},
// ══════════════════════════════════════════════════════════════════════
// Vitest
// ══════════════════════════════════════════════════════════════════════
{
"name": "Vitest: Current File",
"type": "node",
"request": "launch",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"smartStep": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Vitest: All Tests",
"type": "node",
"request": "launch",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run"],
"smartStep": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Vitest: Watch Mode",
"type": "node",
"request": "launch",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["--watch", "${relativeFile}"],
"smartStep": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
// ══════════════════════════════════════════════════════════════════════
// Playwright
// ══════════════════════════════════════════════════════════════════════
{
"name": "Playwright: Current File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/@playwright/test/cli.js",
"args": ["test", "${relativeFile}", "--workers=1"],
"cwd": "${workspaceFolder}",
"autoAttachChildProcesses": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Playwright: Debug Current File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/@playwright/test/cli.js",
"args": ["test", "${relativeFile}", "--workers=1", "--headed"],
"cwd": "${workspaceFolder}",
"env": {
"PWDEBUG": "1"
},
"autoAttachChildProcesses": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
// ══════════════════════════════════════════════════════════════════════
// Python Sidecar
// ══════════════════════════════════════════════════════════════════════
{
"name": "Python: Dispatcher",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/packages/ai/python/dispatcher.py",
"cwd": "${workspaceFolder}/packages/ai",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}/packages/ai",
"console": "integratedTerminal",
"justMyCode": true
},
// ══════════════════════════════════════════════════════════════════════
// Attach
// ══════════════════════════════════════════════════════════════════════
{
"name": "Attach: Node (port 9229)",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"sourceMaps": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"name": "Attach: Python (port 5678)",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"justMyCode": true
}
],
// ══════════════════════════════════════════════════════════════════════
// Compound (multi-process) Configurations
// ══════════════════════════════════════════════════════════════════════
"compounds": [
{
"name": "Full Stack: API + Web",
"configurations": ["API: Fastify (tsx)", "Web: Vite Dev Server"],
"stopAll": true
},
{
"name": "Full Stack: API + Web + Chrome",
"configurations": ["API: Fastify (tsx)", "Web: Vite Dev Server", "Web: Debug in Chrome"],
"stopAll": true,
"presentation": {
"group": "compound",
"order": 1
}
}
]
}
+165
View File
@@ -0,0 +1,165 @@
{
// ── Editor Defaults ──────────────────────────────────────────────────
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.linkedEditing": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit"
},
// ── Biome ─────────────────────────────────────────────────────────────
"biome.enabled": true,
"biome.requireConfig": true,
// Disable competing formatters/linters
"prettier.enable": false,
"eslint.enable": false,
// ── Language-specific formatter overrides ──────────────────────────────
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[python]": {
"editor.defaultFormatter": "ms-python.python",
"editor.tabSize": 4
},
"[markdown]": {
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint",
"editor.wordWrap": "on"
},
"[dockercompose]": {
"editor.defaultFormatter": "ms-azuretools.vscode-docker"
},
"[dockerfile]": {
"editor.defaultFormatter": "ms-azuretools.vscode-docker"
},
"[yaml]": {
"editor.tabSize": 2
},
// ── TypeScript ────────────────────────────────────────────────────────
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.quoteStyle": "double",
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"javascript.preferences.quoteStyle": "double",
"javascript.updateImportsOnFileMove.enabled": "always",
// ── Tailwind CSS ──────────────────────────────────────────────────────
"tailwindCSS.emmetCompletions": true,
"tailwindCSS.includeLanguages": {
"typescriptreact": "html"
},
"tailwindCSS.classAttributes": ["class", "className", "ngClass", "variants"],
// v4 auto-detects from @import "tailwindcss" in CSS files; no configFile needed.
// ── Python (AI sidecar) ───────────────────────────────────────────────
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoSearchPaths": true,
"python.analysis.extraPaths": ["packages/ai/python"],
"python.defaultInterpreterPath": "${workspaceFolder}/packages/ai/.venv/bin/python",
// ── Testing ───────────────────────────────────────────────────────────
"vitest.rootConfig": "vitest.config.ts",
// ── File Associations ─────────────────────────────────────────────────
"files.associations": {
"*.css": "tailwindcss",
".env*": "dotenv",
"Dockerfile*": "dockerfile",
"*.yml": "yaml",
"*.yaml": "yaml"
},
// ── File Exclusions (Explorer) ────────────────────────────────────────
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/.turbo": true
},
// ── Search Exclusions ─────────────────────────────────────────────────
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/coverage": true,
"**/.turbo": true,
"**/playwright-report": true,
"**/test-results": true,
"**/blob-report": true,
"**/pnpm-lock.yaml": true,
"**/apps/api/drizzle": true,
"**/.pnpm-store": true,
"**/.worktrees": true
},
// ── File Watcher Exclusions ───────────────────────────────────────────
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/coverage/**": true,
"**/.turbo/**": true,
"**/playwright-report/**": true,
"**/test-results/**": true,
"**/blob-report/**": true,
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.worktrees/**": true
},
// ── Emmet ─────────────────────────────────────────────────────────────
"emmet.includeLanguages": {
"typescriptreact": "html"
},
// ── Terminal ──────────────────────────────────────────────────────────
"terminal.integrated.defaultProfile.osx": "zsh",
"terminal.integrated.env.osx": {
"NODE_OPTIONS": ""
},
// ── Git ───────────────────────────────────────────────────────────────
"git.autofetch": true,
"git.enableSmartCommit": true,
"git.confirmSync": false,
// ── Misc ──────────────────────────────────────────────────────────────
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"package.json": "pnpm-lock.yaml, pnpm-workspace.yaml, .npmrc, turbo.json",
"tsconfig.json": "tsconfig.*.json",
"tsconfig.base.json": "tsconfig.*.json",
"biome.json": ".editorconfig",
"vite.config.*": "postcss.config.*, tailwind.config.*",
".gitignore": ".gitattributes, .dockerignore",
"Dockerfile": "docker-compose.yml, docker-compose.*.yml, .dockerignore",
"*.ts": "${capture}.test.ts, ${capture}.spec.ts",
"*.tsx": "${capture}.test.tsx, ${capture}.spec.tsx"
}
}
+282
View File
@@ -0,0 +1,282 @@
{
"version": "2.0.0",
"tasks": [
// ══════════════════════════════════════════════════════════════════════
// Dev Servers
// ══════════════════════════════════════════════════════════════════════
{
"label": "dev",
"detail": "Start all dev servers (turbo dev)",
"type": "shell",
"command": "pnpm dev",
"group": "none",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"group": "dev"
}
},
{
"label": "dev:api",
"detail": "Start Fastify API server only",
"type": "shell",
"command": "pnpm --filter @snapotter/api dev",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"group": "dev"
}
},
{
"label": "dev:web",
"detail": "Start Vite frontend only",
"type": "shell",
"command": "pnpm --filter @snapotter/web dev",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"group": "dev"
}
},
{
"label": "dev:docs",
"detail": "Start VitePress docs server",
"type": "shell",
"command": "pnpm docs:dev",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"group": "dev"
}
},
// ══════════════════════════════════════════════════════════════════════
// Build
// ══════════════════════════════════════════════════════════════════════
{
"label": "build",
"detail": "Build all workspaces (turbo build)",
"type": "shell",
"command": "pnpm build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$tsc"],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
},
{
"label": "build:api",
"detail": "Build API workspace",
"type": "shell",
"command": "pnpm --filter @snapotter/api build",
"group": "build",
"problemMatcher": ["$tsc"]
},
{
"label": "build:web",
"detail": "Build Web workspace",
"type": "shell",
"command": "pnpm --filter @snapotter/web build",
"group": "build",
"problemMatcher": ["$tsc"]
},
// ══════════════════════════════════════════════════════════════════════
// Type Check
// ══════════════════════════════════════════════════════════════════════
{
"label": "typecheck",
"detail": "TypeScript check across monorepo",
"type": "shell",
"command": "pnpm typecheck",
"group": "build",
"problemMatcher": ["$tsc"]
},
// ══════════════════════════════════════════════════════════════════════
// Lint & Format
// ══════════════════════════════════════════════════════════════════════
{
"label": "lint",
"detail": "Biome lint + format check",
"type": "shell",
"command": "pnpm lint",
"problemMatcher": [],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
},
{
"label": "lint:fix",
"detail": "Biome auto-fix lint + format issues",
"type": "shell",
"command": "pnpm lint:fix",
"problemMatcher": [],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
},
// ══════════════════════════════════════════════════════════════════════
// Testing
// ══════════════════════════════════════════════════════════════════════
{
"label": "test",
"detail": "Run all Vitest tests",
"type": "shell",
"command": "pnpm test",
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "test:unit",
"detail": "Unit tests only",
"type": "shell",
"command": "pnpm test:unit",
"group": "test",
"problemMatcher": []
},
{
"label": "test:integration",
"detail": "Integration tests only",
"type": "shell",
"command": "pnpm test:integration",
"group": "test",
"problemMatcher": []
},
{
"label": "test:watch",
"detail": "Vitest in watch mode",
"type": "shell",
"command": "pnpm test:watch",
"isBackground": true,
"group": "test",
"problemMatcher": []
},
{
"label": "test:coverage",
"detail": "Tests with coverage report",
"type": "shell",
"command": "pnpm test:coverage",
"group": "test",
"problemMatcher": []
},
{
"label": "test:e2e",
"detail": "Playwright e2e tests",
"type": "shell",
"command": "pnpm test:e2e",
"group": "test",
"problemMatcher": []
},
{
"label": "test:e2e:ui",
"detail": "Playwright e2e with interactive UI",
"type": "shell",
"command": "pnpm test:e2e:ui",
"isBackground": true,
"group": "test",
"problemMatcher": []
},
// ══════════════════════════════════════════════════════════════════════
// Database
// ══════════════════════════════════════════════════════════════════════
{
"label": "db:generate",
"detail": "Generate Drizzle migration from schema changes",
"type": "shell",
"command": "npx drizzle-kit generate",
"options": {
"cwd": "${workspaceFolder}/apps/api"
},
"problemMatcher": []
},
{
"label": "db:migrate",
"detail": "Apply pending Drizzle migrations",
"type": "shell",
"command": "npx drizzle-kit migrate",
"options": {
"cwd": "${workspaceFolder}/apps/api"
},
"problemMatcher": []
},
{
"label": "db:studio",
"detail": "Open Drizzle Studio (database browser)",
"type": "shell",
"command": "npx drizzle-kit studio",
"options": {
"cwd": "${workspaceFolder}/apps/api"
},
"isBackground": true,
"problemMatcher": []
},
// ══════════════════════════════════════════════════════════════════════
// Docker
// ══════════════════════════════════════════════════════════════════════
{
"label": "docker:build",
"detail": "Build Docker image",
"type": "shell",
"command": "docker build -f docker/Dockerfile -t snapotter:local .",
"problemMatcher": []
},
{
"label": "docker:run",
"detail": "Run Docker container locally",
"type": "shell",
"command": "docker run --rm -p 1349:1349 -p 13490:13490 snapotter:local",
"isBackground": true,
"problemMatcher": []
},
{
"label": "docker:test",
"detail": "Run Docker integration tests",
"type": "shell",
"command": "pnpm test:docker",
"problemMatcher": []
},
// ══════════════════════════════════════════════════════════════════════
// Utilities
// ══════════════════════════════════════════════════════════════════════
{
"label": "clean",
"detail": "Clean all dist/build artifacts",
"type": "shell",
"command": "pnpm clean",
"problemMatcher": []
},
{
"label": "install",
"detail": "Install all dependencies",
"type": "shell",
"command": "pnpm install",
"problemMatcher": []
}
]
}