From 9cf6b979da432d643e42147455970ca877edeb5e Mon Sep 17 00:00:00 2001 From: Kirill Dubovitskiy Date: Fri, 18 Jul 2025 23:59:48 -0700 Subject: [PATCH] devx: easier way to run the server + debugger --- .claude/settings.local.json | 9 ----- .cursorrules | 79 +------------------------------------ .env.example | 5 +-- .gitignore | 6 ++- .vscode/launch.json | 30 ++++++++++++++ package.json | 8 ++-- sources/utils/log.ts | 49 ++++++++++++++++++++++- yarn.lock | 29 ++++++++++++++ 8 files changed, 120 insertions(+), 95 deletions(-) delete mode 100644 .claude/settings.local.json mode change 100644 => 120000 .cursorrules create mode 100644 .vscode/launch.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 5951fa7..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(yarn add:*)", - "Bash(yarn build)" - ], - "deny": [] - } -} \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 7244781..0000000 --- a/.cursorrules +++ /dev/null @@ -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. \ No newline at end of file diff --git a/.cursorrules b/.cursorrules new file mode 120000 index 0000000..681311e --- /dev/null +++ b/.cursorrules @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/.env.example b/.env.example index a671511..9277a2e 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/handy -REDIS_URL=redis://localhost:6379 -SEED=your-seed-for-token-generation -PORT=3005 \ No newline at end of file +HANDY_MASTER_SECRET=your-super-secret-key-for-local-development +PORT=3005 diff --git a/.gitignore b/.gitignore index c8cdaa1..d8df5e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ dist .pgdata .env.local -.env \ No newline at end of file +.env + +.logs/ + +.claude/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..47ec8a0 --- /dev/null +++ b/.vscode/launch.json @@ -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": [ + "/**" + ], + "resolveSourceMapLocations": [ + "${workspaceFolder}/**", + "!**/node_modules/**" + ] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index a12046e..703e03a 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,12 @@ "scripts": { "build": "tsc --noEmit", "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", - "migrate": "prisma migrate dev", + "migrate": "dotenv -e .env.local -- prisma migrate dev", "generate": "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": { "@types/chalk": "^2.2.0", @@ -21,6 +21,7 @@ "@types/node": "^20.12.3", "@types/tmp": "^0.2.6", "@types/uuid": "^9.0.8", + "dotenv-cli": "^8.0.0", "ts-node": "^10.9.2", "typescript": "^5.4.3", "yaml": "^2.4.2" @@ -40,6 +41,7 @@ "fastify-type-provider-zod": "^4.0.2", "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.2", + "pino-pretty": "^13.0.0", "prisma": "^6.11.1", "prisma-json-types-generator": "^3.5.1", "privacy-kit": "^0.0.23", diff --git a/sources/utils/log.ts b/sources/utils/log.ts index 240532a..a2d9cc5 100644 --- a/sources/utils/log.ts +++ b/sources/utils/log.ts @@ -1,7 +1,46 @@ 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({ - level: 'info', + level: isDebug ? 'debug' : 'info', + transport: { + targets: transports, + }, }); 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[]) { 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); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4847751..d6ce53c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,15 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" 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: version "4.1.0" 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" 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: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"