@roomi-fields/grillhouse 0.0.0-stage → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +9 -0
- package/METACADRE.md +41 -0
- package/agents/developpeur.md +10 -0
- package/agents/explorateur.md +9 -0
- package/agents/questionneur.md +11 -0
- package/agents/referent-technique.md +9 -0
- package/agents/regulateur.md +11 -0
- package/agents/relecteur.md +9 -0
- package/agents/testwriter.md +10 -0
- package/bin/grillhouse.mjs +125 -0
- package/cadre/templates/projet/.claude/settings.json +30 -0
- package/cadre/templates/projet/.editorconfig +47 -0
- package/cadre/templates/projet/.env.example +57 -0
- package/cadre/templates/projet/.eslintrc.cjs +56 -0
- package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
- package/cadre/templates/projet/.mcp.json +13 -0
- package/cadre/templates/projet/.prettierrc +11 -0
- package/cadre/templates/projet/CHANGELOG.md +61 -0
- package/cadre/templates/projet/CLAUDE.md +46 -0
- package/cadre/templates/projet/CONTRIBUTING.md +243 -0
- package/cadre/templates/projet/CREDITS.md +41 -0
- package/cadre/templates/projet/README.md +152 -0
- package/cadre/templates/projet/_gitignore +91 -0
- package/cadre/templates/projet/_npmignore +46 -0
- package/cadre/templates/projet/deployment/README.md +98 -0
- package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
- package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
- package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
- package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
- package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
- package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
- package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
- package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
- package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
- package/cadre/templates/projet/ecosystem.config.cjs +73 -0
- package/cadre/templates/projet/package.json +90 -0
- package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
- package/cadre/templates/projet/src/config.ts +68 -0
- package/cadre/templates/projet/src/errors.ts +102 -0
- package/cadre/templates/projet/src/index.ts +46 -0
- package/cadre/templates/projet/src/services/.gitkeep +8 -0
- package/cadre/templates/projet/src/types/index.ts +24 -0
- package/cadre/templates/projet/src/utils/logger.ts +106 -0
- package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
- package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
- package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
- package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
- package/cadre/templates/projet/tsconfig.eslint.json +9 -0
- package/cadre/templates/projet/tsconfig.json +41 -0
- package/cadre/templates/projet/tsdoc.json +6 -0
- package/cadre/templates/projet/vitest.config.ts +44 -0
- package/cadre/templates/turbo/paquets.json +29 -0
- package/cadre/templates/turbo/sans-paquets.json +28 -0
- package/charte.md +107 -0
- package/docs/ARCHITECTURE.md +90 -0
- package/hooks/crochet.mjs +33 -0
- package/hooks/hooks.json +58 -0
- package/hooks/register.tsx +558 -0
- package/package.json +75 -4
- package/scripts/avancement.mjs +244 -0
- package/scripts/bilan.mjs +523 -0
- package/scripts/branche-de-nuit.mjs +59 -0
- package/scripts/cadre.mjs +109 -0
- package/scripts/circuit.mjs +4375 -0
- package/scripts/code-mort.mjs +371 -0
- package/scripts/composants-affectes.mjs +598 -0
- package/scripts/consommateurs-arbitres.mjs +192 -0
- package/scripts/consommateurs.mjs +310 -0
- package/scripts/crochet.mjs +24 -0
- package/scripts/crochets.mjs +135 -0
- package/scripts/dernier-vert.mjs +208 -0
- package/scripts/dette.mjs +181 -0
- package/scripts/droits.mjs +354 -0
- package/scripts/enveloppe/codes.mjs +17 -0
- package/scripts/enveloppe/composant.mjs +11 -0
- package/scripts/enveloppe/enveloppe.mjs +628 -0
- package/scripts/enveloppe/lancer.sh +311 -0
- package/scripts/etapes.mjs +323 -0
- package/scripts/etat-du-circuit.mjs +104 -0
- package/scripts/frontieres.mjs +456 -0
- package/scripts/gardes-du-cadre.mjs +72 -0
- package/scripts/greffons.mjs +338 -0
- package/scripts/grillhouse-maj.mjs +514 -0
- package/scripts/index-interfaces.mjs +293 -0
- package/scripts/init.mjs +117 -0
- package/scripts/integration/file-attente.mjs +61 -0
- package/scripts/integration/file.mjs +494 -0
- package/scripts/integration/integrer.mjs +381 -0
- package/scripts/integration/rejeu.mjs +139 -0
- package/scripts/integration/verdict.mjs +13 -0
- package/scripts/interfaces-contre-code.mjs +547 -0
- package/scripts/ligne-etat/defaut.sh +110 -0
- package/scripts/ligne-etat/ligne-etat.mjs +93 -0
- package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
- package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
- package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
- package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
- package/scripts/migrations.mjs +43 -0
- package/scripts/nuit.sh +269 -0
- package/scripts/paquets.mjs +79 -0
- package/scripts/poussee-cles.mjs +46 -0
- package/scripts/preparation.mjs +84 -0
- package/scripts/references-par-les-types.mjs +420 -0
- package/scripts/registre.mjs +336 -0
- package/scripts/resultats.mjs +580 -0
- package/scripts/resumes.mjs +57 -0
- package/scripts/retirer-copie.sh +127 -0
- package/scripts/session-start.sh +203 -0
- package/scripts/sortie.mjs +155 -0
- package/scripts/sous-modules.sh +316 -0
- package/scripts/structure-guard.sh +27 -0
- package/scripts/superviseur.mjs +102 -0
- package/scripts/tableau.mjs +1886 -0
- package/scripts/tests.mjs +236 -0
- package/scripts/ticket.sh +23 -0
- package/scripts/turbo-des-paquets.mjs +899 -0
- package/scripts/un-commit-par-ticket.mjs +212 -0
- package/scripts/verifier.mjs +469 -0
- package/scripts/verrou-de-fichier.mjs +130 -0
- package/scripts/verrou-de-machine.mjs +211 -0
- package/scripts/verrous/commande.mjs +180 -0
- package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
- package/scripts/verrous/verrou.mjs +165 -0
- package/scripts/vitest-config/avancement.mjs +39 -0
- package/scripts/vitest-config/avancement.reporter.mjs +56 -0
- package/scripts/vitest-config/base-de-passe.mjs +128 -0
- package/scripts/vitest-config/base.d.ts +6 -0
- package/scripts/vitest-config/base.mjs +87 -0
- package/scripts/vitest-config/changements.mjs +173 -0
- package/scripts/vitest-config/cibles.mjs +207 -0
- package/scripts/vitest-config/duree.reporter.mjs +98 -0
- package/scripts/vitest-config/machine.setup.mjs +14 -0
- package/scripts/vitest-config/mesure.d.ts +6 -0
- package/scripts/vitest-config/mesure.mjs +13 -0
- package/scripts/vitest-config/mesure.setup.mjs +12 -0
- package/scripts/vitest-config/pression.mjs +90 -0
- package/scripts/vitest-config/resultats.mjs +23 -0
- package/scripts/vitest-config/resultats.setup.mjs +19 -0
- package/scripts/vitest.nuit.config.mjs +26 -0
- package/skills/developpeur/SKILL.md +175 -0
- package/skills/explorateur/SKILL.md +84 -0
- package/skills/grill/SKILL.md +238 -0
- package/skills/grill/references/releve-structurel.md +38 -0
- package/skills/mesure/SKILL.md +168 -0
- package/skills/pitmaster/SKILL.md +382 -0
- package/skills/pitmaster/references/cadre.md +119 -0
- package/skills/pitmaster/references/consigne-agent.md +308 -0
- package/skills/pitmaster/references/erreurs.md +25 -0
- package/skills/pitmaster/references/tour.md +124 -0
- package/skills/questionneur/SKILL.md +64 -0
- package/skills/redacteur/SKILL.md +84 -0
- package/skills/redacteur/references/documents-d-un-composant.md +105 -0
- package/skills/referent-technique/SKILL.md +145 -0
- package/skills/regulateur/SKILL.md +220 -0
- package/skills/release/SKILL.md +134 -0
- package/skills/release/scripts/update-version.cjs +92 -0
- package/skills/relecteur/SKILL.md +152 -0
- package/skills/relecteur/references/LICENSE-MIT +30 -0
- package/skills/relecteur/references/SOURCE.md +8 -0
- package/skills/relecteur/references/claims-check.md +14 -0
- package/skills/relecteur/references/deletion-check.md +14 -0
- package/skills/relecteur/references/edge-case-hunter.md +80 -0
- package/skills/relecteur/references/verification-gap.md +113 -0
- package/skills/testwriter/SKILL.md +187 -0
- package/types/index.d.ts +56 -0
- package/README.md +0 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@your-scope/project-name",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A brief description of your project",
|
|
5
|
+
"author": "Your Name <your.email@example.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"project-name": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"watch": "tsc --watch",
|
|
16
|
+
"dev": "tsx watch src/index.ts",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "grillhouse tests",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"lint": "eslint src tests --ext .ts,.js,.mjs",
|
|
23
|
+
"lint:fix": "eslint src tests --ext .ts,.js,.mjs --fix",
|
|
24
|
+
"format": "prettier --write \"src/**/*.{ts,js,mjs}\" \"tests/**/*.{ts,js,mjs}\"",
|
|
25
|
+
"format:check": "prettier --check \"src/**/*.{ts,js,mjs}\" \"tests/**/*.{ts,js,mjs}\"",
|
|
26
|
+
"prepare": "grillhouse hooks && npm run build",
|
|
27
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm run verifier && npm run build",
|
|
28
|
+
"daemon:start": "pm2 start ecosystem.config.cjs",
|
|
29
|
+
"daemon:stop": "pm2 stop project-name",
|
|
30
|
+
"daemon:restart": "pm2 restart project-name",
|
|
31
|
+
"daemon:logs": "pm2 logs project-name",
|
|
32
|
+
"daemon:status": "pm2 status",
|
|
33
|
+
"daemon:delete": "pm2 delete project-name",
|
|
34
|
+
"interfaces": "grillhouse index-interfaces",
|
|
35
|
+
"grillhouse:gardes": "grillhouse gardes-du-cadre",
|
|
36
|
+
"frontieres": "grillhouse frontieres",
|
|
37
|
+
"api": "grillhouse interfaces-contre-code --ecrire",
|
|
38
|
+
"tableau": "grillhouse tableau --texte",
|
|
39
|
+
"grillhouse:maj": "grillhouse maj",
|
|
40
|
+
"code-mort": "grillhouse code-mort",
|
|
41
|
+
"test:vitest": "vitest run",
|
|
42
|
+
"test:forcer": "grillhouse tests --force",
|
|
43
|
+
"verifier": "grillhouse verifier",
|
|
44
|
+
"grillhouse:dette:inscrire": "turbo run build && GRILLHOUSE_DETTE=inscrire npm run --silent grillhouse:gardes",
|
|
45
|
+
"grillhouse:dette:elaguer": "turbo run build && GRILLHOUSE_DETTE=elaguer npm run --silent grillhouse:gardes"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"keyword1",
|
|
49
|
+
"keyword2",
|
|
50
|
+
"keyword3"
|
|
51
|
+
],
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/your-username/project-name.git"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/your-username/project-name/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/your-username/project-name#readme",
|
|
60
|
+
"files": [
|
|
61
|
+
"dist",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"docs"
|
|
65
|
+
],
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22.0.0"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"dotenv": "^16.4.0",
|
|
71
|
+
"zod": "^3.22.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@microsoft/api-extractor": "7.59.2",
|
|
75
|
+
"@types/node": "^20.0.0",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
77
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
78
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
79
|
+
"dependency-cruiser": "18.4.0",
|
|
80
|
+
"eslint": "^8.57.0",
|
|
81
|
+
"knip": "^6.41.0",
|
|
82
|
+
"pm2": "^5.4.2",
|
|
83
|
+
"prettier": "^3.2.0",
|
|
84
|
+
"tsx": "^4.7.0",
|
|
85
|
+
"turbo": "2.11.8",
|
|
86
|
+
"typescript": "^5.3.3",
|
|
87
|
+
"vitest": "^2.0.0"
|
|
88
|
+
},
|
|
89
|
+
"packageManager": "npm@10.9.8"
|
|
90
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Suivi de la supervision
|
|
2
|
+
|
|
3
|
+
Tenu par la compétence `pitmaster`. Une question s'écrit ici dès qu'elle naît ; tranchée, elle en
|
|
4
|
+
sort et sa réponse va dans son ticket.
|
|
5
|
+
|
|
6
|
+
## Dernier tour
|
|
7
|
+
|
|
8
|
+
- Date : aucun tour encore
|
|
9
|
+
- Agents lancés : aucun
|
|
10
|
+
- À vérifier au prochain tour : rien
|
|
11
|
+
|
|
12
|
+
## R — à confirmer par le responsable
|
|
13
|
+
|
|
14
|
+
Chaque point : contexte, exemple, règle, recommandation, question, ticket.
|
|
15
|
+
|
|
16
|
+
## S — à surveiller
|
|
17
|
+
|
|
18
|
+
Chaque point : ce qui se surveille, ce qui le fermera.
|
|
19
|
+
|
|
20
|
+
## F — fermés
|
|
21
|
+
|
|
22
|
+
Chaque point : ce qui l'a fermé, le ticket.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application configuration
|
|
3
|
+
*
|
|
4
|
+
* Centralizes all configuration from environment variables
|
|
5
|
+
* with sensible defaults for development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
// Load environment variables
|
|
12
|
+
dotenvConfig();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Environment configuration schema
|
|
16
|
+
*/
|
|
17
|
+
const envSchema = z.object({
|
|
18
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
19
|
+
PORT: z.string().transform(Number).default('3000'),
|
|
20
|
+
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse and validate environment variables
|
|
25
|
+
*/
|
|
26
|
+
function parseEnv() {
|
|
27
|
+
const result = envSchema.safeParse(process.env);
|
|
28
|
+
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
console.error('Invalid environment configuration:');
|
|
31
|
+
console.error(result.error.format());
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result.data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const env = parseEnv();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Application configuration object
|
|
42
|
+
*/
|
|
43
|
+
export const config = {
|
|
44
|
+
/** Application version from package.json */
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
|
|
47
|
+
/** Current environment */
|
|
48
|
+
env: env.NODE_ENV,
|
|
49
|
+
|
|
50
|
+
/** Is development mode */
|
|
51
|
+
isDev: env.NODE_ENV === 'development',
|
|
52
|
+
|
|
53
|
+
/** Is production mode */
|
|
54
|
+
isProd: env.NODE_ENV === 'production',
|
|
55
|
+
|
|
56
|
+
/** Is test mode */
|
|
57
|
+
isTest: env.NODE_ENV === 'test',
|
|
58
|
+
|
|
59
|
+
/** Server configuration */
|
|
60
|
+
server: {
|
|
61
|
+
port: env.PORT,
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/** Logging configuration */
|
|
65
|
+
logging: {
|
|
66
|
+
level: env.LOG_LEVEL,
|
|
67
|
+
},
|
|
68
|
+
} as const;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes
|
|
3
|
+
*
|
|
4
|
+
* Define application-specific errors for better error handling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base application error
|
|
9
|
+
*/
|
|
10
|
+
export class AppError extends Error {
|
|
11
|
+
public readonly code: string;
|
|
12
|
+
public readonly statusCode: number;
|
|
13
|
+
public readonly isOperational: boolean;
|
|
14
|
+
|
|
15
|
+
constructor(message: string, code = 'INTERNAL_ERROR', statusCode = 500, isOperational = true) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = this.constructor.name;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
this.isOperational = isOperational;
|
|
21
|
+
|
|
22
|
+
// Maintains proper stack trace
|
|
23
|
+
Error.captureStackTrace(this, this.constructor);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validation error (400)
|
|
29
|
+
*/
|
|
30
|
+
export class ValidationError extends AppError {
|
|
31
|
+
constructor(message: string) {
|
|
32
|
+
super(message, 'VALIDATION_ERROR', 400);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Not found error (404)
|
|
38
|
+
*/
|
|
39
|
+
export class NotFoundError extends AppError {
|
|
40
|
+
constructor(resource: string) {
|
|
41
|
+
super(`${resource} not found`, 'NOT_FOUND', 404);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Authentication error (401)
|
|
47
|
+
*/
|
|
48
|
+
export class AuthenticationError extends AppError {
|
|
49
|
+
constructor(message = 'Authentication required') {
|
|
50
|
+
super(message, 'AUTHENTICATION_ERROR', 401);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Authorization error (403)
|
|
56
|
+
*/
|
|
57
|
+
export class AuthorizationError extends AppError {
|
|
58
|
+
constructor(message = 'Permission denied') {
|
|
59
|
+
super(message, 'AUTHORIZATION_ERROR', 403);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Conflict error (409)
|
|
65
|
+
*/
|
|
66
|
+
export class ConflictError extends AppError {
|
|
67
|
+
constructor(message: string) {
|
|
68
|
+
super(message, 'CONFLICT_ERROR', 409);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Rate limit error (429)
|
|
74
|
+
*/
|
|
75
|
+
export class RateLimitError extends AppError {
|
|
76
|
+
constructor(message = 'Too many requests') {
|
|
77
|
+
super(message, 'RATE_LIMIT_ERROR', 429);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* External service error (502)
|
|
83
|
+
*/
|
|
84
|
+
export class ExternalServiceError extends AppError {
|
|
85
|
+
constructor(service: string, originalError?: Error) {
|
|
86
|
+
super(
|
|
87
|
+
`External service error: ${service}${originalError ? ` - ${originalError.message}` : ''}`,
|
|
88
|
+
'EXTERNAL_SERVICE_ERROR',
|
|
89
|
+
502
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if error is operational (expected) vs programming error
|
|
96
|
+
*/
|
|
97
|
+
export function isOperationalError(error: unknown): boolean {
|
|
98
|
+
if (error instanceof AppError) {
|
|
99
|
+
return error.isOperational;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Main entry point
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { config } from './config.js';
|
|
9
|
+
import { logger } from './utils/logger.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main application function
|
|
13
|
+
*/
|
|
14
|
+
function main(): void {
|
|
15
|
+
logger.info('Application starting...', { version: config.version });
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Initialize your application here
|
|
19
|
+
logger.info('Application started successfully');
|
|
20
|
+
|
|
21
|
+
// Keep the process running (remove if not needed)
|
|
22
|
+
// await new Promise(() => {});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
logger.error('Application failed to start', { error });
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle graceful shutdown
|
|
30
|
+
process.on('SIGINT', () => {
|
|
31
|
+
logger.info('Received SIGINT, shutting down...');
|
|
32
|
+
process.exit(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
process.on('SIGTERM', () => {
|
|
36
|
+
logger.info('Received SIGTERM, shutting down...');
|
|
37
|
+
process.exit(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Handle unhandled rejections
|
|
41
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
42
|
+
logger.error('Unhandled Rejection', { reason, promise });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Run main
|
|
46
|
+
main();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions
|
|
3
|
+
*
|
|
4
|
+
* Export all shared types from this module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generic result type for operations that can fail
|
|
9
|
+
*/
|
|
10
|
+
export type Result<T, E = Error> = { success: true; data: T } | { success: false; error: E };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a success result
|
|
14
|
+
*/
|
|
15
|
+
export function success<T>(data: T): Result<T, never> {
|
|
16
|
+
return { success: true, data };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a failure result
|
|
21
|
+
*/
|
|
22
|
+
export function failure<E>(error: E): Result<never, E> {
|
|
23
|
+
return { success: false, error };
|
|
24
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility
|
|
3
|
+
*
|
|
4
|
+
* Simple structured logging with levels and context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
|
|
9
|
+
interface LogContext {
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
14
|
+
debug: 0,
|
|
15
|
+
info: 1,
|
|
16
|
+
warn: 2,
|
|
17
|
+
error: 3,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get current log level from environment
|
|
22
|
+
*/
|
|
23
|
+
function getCurrentLevel(): LogLevel {
|
|
24
|
+
const level = process.env.LOG_LEVEL?.toLowerCase() as LogLevel;
|
|
25
|
+
return LOG_LEVELS[level] !== undefined ? level : 'info';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format log message with timestamp and context
|
|
30
|
+
*/
|
|
31
|
+
function formatMessage(level: LogLevel, message: string, context?: LogContext): string {
|
|
32
|
+
const timestamp = new Date().toISOString();
|
|
33
|
+
const levelStr = level.toUpperCase().padEnd(5);
|
|
34
|
+
|
|
35
|
+
let output = `[${timestamp}] ${levelStr} ${message}`;
|
|
36
|
+
|
|
37
|
+
if (context && Object.keys(context).length > 0) {
|
|
38
|
+
output += ` ${JSON.stringify(context)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return output;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if log level should be output
|
|
46
|
+
*/
|
|
47
|
+
function shouldLog(level: LogLevel): boolean {
|
|
48
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[getCurrentLevel()];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Logger instance
|
|
53
|
+
*/
|
|
54
|
+
export const logger = {
|
|
55
|
+
/**
|
|
56
|
+
* Log debug message
|
|
57
|
+
*/
|
|
58
|
+
debug(message: string, context?: LogContext): void {
|
|
59
|
+
if (shouldLog('debug')) {
|
|
60
|
+
console.debug(formatMessage('debug', message, context));
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Log info message
|
|
66
|
+
*/
|
|
67
|
+
info(message: string, context?: LogContext): void {
|
|
68
|
+
if (shouldLog('info')) {
|
|
69
|
+
console.info(formatMessage('info', message, context));
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Log warning message
|
|
75
|
+
*/
|
|
76
|
+
warn(message: string, context?: LogContext): void {
|
|
77
|
+
if (shouldLog('warn')) {
|
|
78
|
+
console.warn(formatMessage('warn', message, context));
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Log error message
|
|
84
|
+
*/
|
|
85
|
+
error(message: string, context?: LogContext): void {
|
|
86
|
+
if (shouldLog('error')) {
|
|
87
|
+
console.error(formatMessage('error', message, context));
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a child logger with preset context
|
|
93
|
+
*/
|
|
94
|
+
child(baseContext: LogContext) {
|
|
95
|
+
return {
|
|
96
|
+
debug: (message: string, context?: LogContext) =>
|
|
97
|
+
logger.debug(message, { ...baseContext, ...context }),
|
|
98
|
+
info: (message: string, context?: LogContext) =>
|
|
99
|
+
logger.info(message, { ...baseContext, ...context }),
|
|
100
|
+
warn: (message: string, context?: LogContext) =>
|
|
101
|
+
logger.warn(message, { ...baseContext, ...context }),
|
|
102
|
+
error: (message: string, context?: LogContext) =>
|
|
103
|
+
logger.error(message, { ...baseContext, ...context }),
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config module tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('Config', () => {
|
|
8
|
+
const originalEnv = process.env;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Reset modules to get fresh config
|
|
12
|
+
vi.resetModules();
|
|
13
|
+
process.env = { ...originalEnv };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
process.env = originalEnv;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('environment detection', () => {
|
|
21
|
+
it('should default to development', async () => {
|
|
22
|
+
delete process.env.NODE_ENV;
|
|
23
|
+
const { config } = await import('../../src/config.js');
|
|
24
|
+
expect(config.isDev).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should detect production environment', async () => {
|
|
28
|
+
process.env.NODE_ENV = 'production';
|
|
29
|
+
const { config } = await import('../../src/config.js');
|
|
30
|
+
expect(config.isProd).toBe(true);
|
|
31
|
+
expect(config.isDev).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should detect test environment', async () => {
|
|
35
|
+
process.env.NODE_ENV = 'test';
|
|
36
|
+
const { config } = await import('../../src/config.js');
|
|
37
|
+
expect(config.isTest).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('server configuration', () => {
|
|
42
|
+
it('should use default port', async () => {
|
|
43
|
+
delete process.env.PORT;
|
|
44
|
+
const { config } = await import('../../src/config.js');
|
|
45
|
+
expect(config.server.port).toBe(3000);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should use custom port from environment', async () => {
|
|
49
|
+
process.env.PORT = '8080';
|
|
50
|
+
const { config } = await import('../../src/config.js');
|
|
51
|
+
expect(config.server.port).toBe(8080);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('logging configuration', () => {
|
|
56
|
+
it('should default to info log level', async () => {
|
|
57
|
+
delete process.env.LOG_LEVEL;
|
|
58
|
+
const { config } = await import('../../src/config.js');
|
|
59
|
+
expect(config.logging.level).toBe('info');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should use custom log level', async () => {
|
|
63
|
+
process.env.LOG_LEVEL = 'debug';
|
|
64
|
+
const { config } = await import('../../src/config.js');
|
|
65
|
+
expect(config.logging.level).toBe('debug');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
AppError,
|
|
8
|
+
ValidationError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
AuthenticationError,
|
|
11
|
+
AuthorizationError,
|
|
12
|
+
ConflictError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
ExternalServiceError,
|
|
15
|
+
isOperationalError,
|
|
16
|
+
} from '../../src/errors.js';
|
|
17
|
+
|
|
18
|
+
describe('Error Classes', () => {
|
|
19
|
+
describe('AppError', () => {
|
|
20
|
+
it('should create error with default values', () => {
|
|
21
|
+
const error = new AppError('Test error');
|
|
22
|
+
expect(error.message).toBe('Test error');
|
|
23
|
+
expect(error.code).toBe('INTERNAL_ERROR');
|
|
24
|
+
expect(error.statusCode).toBe(500);
|
|
25
|
+
expect(error.isOperational).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create error with custom values', () => {
|
|
29
|
+
const error = new AppError('Custom error', 'CUSTOM_CODE', 418, false);
|
|
30
|
+
expect(error.message).toBe('Custom error');
|
|
31
|
+
expect(error.code).toBe('CUSTOM_CODE');
|
|
32
|
+
expect(error.statusCode).toBe(418);
|
|
33
|
+
expect(error.isOperational).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should have proper stack trace', () => {
|
|
37
|
+
const error = new AppError('Test');
|
|
38
|
+
expect(error.stack).toBeDefined();
|
|
39
|
+
expect(error.stack).toContain('AppError');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('ValidationError', () => {
|
|
44
|
+
it('should create validation error', () => {
|
|
45
|
+
const error = new ValidationError('Invalid input');
|
|
46
|
+
expect(error.message).toBe('Invalid input');
|
|
47
|
+
expect(error.code).toBe('VALIDATION_ERROR');
|
|
48
|
+
expect(error.statusCode).toBe(400);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('NotFoundError', () => {
|
|
53
|
+
it('should create not found error', () => {
|
|
54
|
+
const error = new NotFoundError('User');
|
|
55
|
+
expect(error.message).toBe('User not found');
|
|
56
|
+
expect(error.code).toBe('NOT_FOUND');
|
|
57
|
+
expect(error.statusCode).toBe(404);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('AuthenticationError', () => {
|
|
62
|
+
it('should create authentication error with default message', () => {
|
|
63
|
+
const error = new AuthenticationError();
|
|
64
|
+
expect(error.message).toBe('Authentication required');
|
|
65
|
+
expect(error.statusCode).toBe(401);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should create authentication error with custom message', () => {
|
|
69
|
+
const error = new AuthenticationError('Invalid token');
|
|
70
|
+
expect(error.message).toBe('Invalid token');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('AuthorizationError', () => {
|
|
75
|
+
it('should create authorization error', () => {
|
|
76
|
+
const error = new AuthorizationError();
|
|
77
|
+
expect(error.message).toBe('Permission denied');
|
|
78
|
+
expect(error.statusCode).toBe(403);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('ConflictError', () => {
|
|
83
|
+
it('should create conflict error', () => {
|
|
84
|
+
const error = new ConflictError('Resource already exists');
|
|
85
|
+
expect(error.statusCode).toBe(409);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('RateLimitError', () => {
|
|
90
|
+
it('should create rate limit error', () => {
|
|
91
|
+
const error = new RateLimitError();
|
|
92
|
+
expect(error.statusCode).toBe(429);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('ExternalServiceError', () => {
|
|
97
|
+
it('should create external service error', () => {
|
|
98
|
+
const error = new ExternalServiceError('PaymentAPI');
|
|
99
|
+
expect(error.message).toBe('External service error: PaymentAPI');
|
|
100
|
+
expect(error.statusCode).toBe(502);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should include original error message', () => {
|
|
104
|
+
const original = new Error('Connection timeout');
|
|
105
|
+
const error = new ExternalServiceError('PaymentAPI', original);
|
|
106
|
+
expect(error.message).toBe('External service error: PaymentAPI - Connection timeout');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('isOperationalError', () => {
|
|
111
|
+
it('should return true for operational errors', () => {
|
|
112
|
+
const error = new AppError('Test', 'TEST', 400, true);
|
|
113
|
+
expect(isOperationalError(error)).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return false for non-operational errors', () => {
|
|
117
|
+
const error = new AppError('Test', 'TEST', 500, false);
|
|
118
|
+
expect(isOperationalError(error)).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return false for standard errors', () => {
|
|
122
|
+
const error = new Error('Standard error');
|
|
123
|
+
expect(isOperationalError(error)).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return false for non-error values', () => {
|
|
127
|
+
expect(isOperationalError(null)).toBe(false);
|
|
128
|
+
expect(isOperationalError(undefined)).toBe(false);
|
|
129
|
+
expect(isOperationalError('string')).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|