devx: easier way to run the server + debugger

This commit is contained in:
Kirill Dubovitskiy 2025-07-18 23:59:48 -07:00
parent 40b98cdc17
commit 9cf6b979da
8 changed files with 120 additions and 95 deletions

View File

@ -1,9 +0,0 @@
{
"permissions": {
"allow": [
"Bash(yarn add:*)",
"Bash(yarn build)"
],
"deny": []
}
}

View File

@ -1,78 +0,0 @@
You are an expert in Typescript and Node.js. You are building a web server in opponiated framework.
Tool usage
- When in doubt - use web tool to get answers from the web.
- Search web when you have some failures.
Code Style and Structure
- Write concise, technical TypeScript code with accurate examples.
- Use functional and declarative programming patterns; avoid classes.
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
- All sources must be imported using "@/" prefix. `import "@/utils/log"` means `import "/sources/utils/log.ts"`.
- Always use absolute imports.
Folder structure
- Root of the sources is "/sources"
- Non-application logic files are in "/sources/modules"
- Low level or too abstract utilities are in "/sources/utils"
- Recipes, ie scripts that you want to run outside of the server are in "/sources/recipes"
- Applications are in /sources/apps
- API server is in /sources/apps/api with routes in /sources/apps/api/routes
- Modules are in /sources/modules
Naming Conventions
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
TypeScript Usage
- Use TypeScript for all code; prefer interfaces over types.
- Avoid enums; use maps instead.
- Use functional components with TypeScript interfaces.
- Use strict mode in TypeScript for better type safety.
Tests
- Write tests using Vitest.
- Always name test files in the same way as the source file, but with ".spec.ts" suffix.
Utilities
- When writing utility function always name file and function in the same way, so it is easy to find it.
- Utility functions should be modular and not too complex.
- When writing utility functions, always write tests for them BEFORE writing the code.
- Iterate your implementation and tests until you are sure that the function works as expected.
- When writing utility functions, always write documentation for them.
Modules
- Modules are bigger than utility functions and their goal is to abstract away complexity
- Each module should have dedicated directory.
- Modules usually don't have application specific logic, they are more general and could be copied to other projects.
- Modules can depend on other modules, but should not depend on application specific logic.
- Prefer to write code as modules instead of application specific code.
- When using a module is a good idea:
- When you need to integrate with external services
- When you need to abstract away complexity of some library
- When you want to implement some group of functions that are related to each other, like math, date, etc.
- Some known modules are:
- ai: AI wrappers to interact with AI services
- eventbus: event bus to send and receive events between modules and applications
- lock: simple lock to synchronize access to resources in the whole cluster
- media: tools to work with media files
Applications
- Applications has some application specific logic
- Applications have the most of the complexity and other parts of the code should assist by reducing complexity here.
- When using prompts, write them to "_prompts.ts" file relative to the application.
Database
- Prisma is used as ORM.
- Use "inTx" to wrap database operations in transactions.
- Do not update schema without absolute necessity.
- For complex fields, use "Json" type.
Events
- eventbus allows to send and receive events inside of the process and between different processes.
- eventbus is local or redis based.
- use "afterTx" to send events after transaction is committed successfully instead of directly emitting events.
Libraries
- Always use Zod for validation of inputs.
- Always use axios for HTTP requests.

1
.cursorrules Symbolic link
View File

@ -0,0 +1 @@
CLAUDE.md

View File

@ -1,4 +1,3 @@
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/handy DATABASE_URL=postgresql://postgres:postgres@localhost:5432/handy
REDIS_URL=redis://localhost:6379 HANDY_MASTER_SECRET=your-super-secret-key-for-local-development
SEED=your-seed-for-token-generation PORT=3005
PORT=3005

6
.gitignore vendored
View File

@ -4,4 +4,8 @@ dist
.pgdata .pgdata
.env.local .env.local
.env .env
.logs/
.claude/

30
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,30 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Server",
"runtimeExecutable": "npx",
"runtimeArgs": [
"tsx",
"--inspect",
"./sources/main.ts"
],
"env": {
"NODE_ENV": "development"
},
"envFile": "${workspaceFolder}/.env.local",
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"<node_internals>/**"
],
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
}
]
}

View File

@ -8,12 +8,12 @@
"scripts": { "scripts": {
"build": "tsc --noEmit", "build": "tsc --noEmit",
"start": "tsx ./sources/main.ts", "start": "tsx ./sources/main.ts",
"dev": "lsof -ti tcp:3005 | xargs kill -9 && tsx --env-file=.env.local ./sources/main.ts", "dev": "tsx --env-file=.env.local ./sources/main.ts",
"test": "vitest", "test": "vitest",
"migrate": "prisma migrate dev", "migrate": "dotenv -e .env.local -- prisma migrate dev",
"generate": "prisma generate", "generate": "prisma generate",
"postinstall": "prisma generate", "postinstall": "prisma generate",
"db": "docker run -e POSTGRES_PASSWORD=postgres -v $(pwd)/.pgdata:/var/lib/postgresql/data -p 5432:5432 postgres" "db": "docker run -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=handy -v $(pwd)/.pgdata:/var/lib/postgresql/data -p 5432:5432 postgres"
}, },
"devDependencies": { "devDependencies": {
"@types/chalk": "^2.2.0", "@types/chalk": "^2.2.0",
@ -21,6 +21,7 @@
"@types/node": "^20.12.3", "@types/node": "^20.12.3",
"@types/tmp": "^0.2.6", "@types/tmp": "^0.2.6",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"dotenv-cli": "^8.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.3", "typescript": "^5.4.3",
"yaml": "^2.4.2" "yaml": "^2.4.2"
@ -40,6 +41,7 @@
"fastify-type-provider-zod": "^4.0.2", "fastify-type-provider-zod": "^4.0.2",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"pino-pretty": "^13.0.0",
"prisma": "^6.11.1", "prisma": "^6.11.1",
"prisma-json-types-generator": "^3.5.1", "prisma-json-types-generator": "^3.5.1",
"privacy-kit": "^0.0.23", "privacy-kit": "^0.0.23",

View File

@ -1,7 +1,46 @@
import pino from 'pino'; import pino from 'pino';
import { mkdirSync } from 'fs';
import { join } from 'path';
const isDebug = process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development';
const logsDir = join(process.cwd(), '.logs');
if (isDebug) {
try {
mkdirSync(logsDir, { recursive: true });
} catch (error) {
console.error('Failed to create logs directory:', error);
}
}
const transports: any[] = [];
transports.push({
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss.l',
ignore: 'pid,hostname',
messageFormat: '{levelLabel} [{time}] {msg}',
errorLikeObjectKeys: ['err', 'error'],
},
});
if (isDebug) {
transports.push({
target: 'pino/file',
options: {
destination: join(logsDir, `server-${new Date().toISOString().split('T')[0]}.log`),
mkdir: true,
},
});
}
export const logger = pino({ export const logger = pino({
level: 'info', level: isDebug ? 'debug' : 'info',
transport: {
targets: transports,
},
}); });
export function log(src: any, ...args: any[]) { export function log(src: any, ...args: any[]) {
@ -10,4 +49,12 @@ export function log(src: any, ...args: any[]) {
export function warn(src: any, ...args: any[]) { export function warn(src: any, ...args: any[]) {
logger.warn(src, ...args); logger.warn(src, ...args);
}
export function error(src: any, ...args: any[]) {
logger.error(src, ...args);
}
export function debug(src: any, ...args: any[]) {
logger.debug(src, ...args);
} }

View File

@ -1140,6 +1140,15 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
date-fns@^4.1.0: date-fns@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
@ -1196,6 +1205,26 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dotenv-cli@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-8.0.0.tgz#cea1519f5a06c7372a1428fca4605fcf3d50e1cf"
integrity sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==
dependencies:
cross-spawn "^7.0.6"
dotenv "^16.3.0"
dotenv-expand "^10.0.0"
minimist "^1.2.6"
dotenv-expand@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37"
integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==
dotenv@^16.3.0:
version "16.6.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020"
integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==
dotenv@^16.4.5: dotenv@^16.4.5:
version "16.4.5" version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"