@synergyerp/backend-standards 1.0.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.
@@ -0,0 +1,52 @@
1
+ ## Description
2
+
3
+ <!-- Provide a clear and concise description of the change. -->
4
+
5
+ ## Type of Change
6
+
7
+ - [ ] feat: New feature
8
+ - [ ] fix: Bug fix
9
+ - [ ] security: Security-related fix
10
+ - [ ] perf: Performance improvement
11
+ - [ ] refactor: Code restructure
12
+ - [ ] test: Test additions/updates
13
+ - [ ] docs: Documentation
14
+ - [ ] chore: Build/tooling/dependencies
15
+ - [ ] ci: CI/CD changes
16
+
17
+ ## Breaking Change?
18
+
19
+ - [ ] Yes (describe below)
20
+ - [ ] No
21
+
22
+ ## Testing Done
23
+
24
+ - [ ] Unit tests added/updated
25
+ - [ ] Integration tests added/updated
26
+ - [ ] Manual testing performed
27
+ - [ ] No testing required (explain why)
28
+
29
+ ## Module Isolation Compliance
30
+
31
+ - [ ] New module has an `index.ts` barrel file
32
+ - [ ] Barrel only exports the service (NOT repository, controller, or validation)
33
+ - [ ] No cross-module deep imports (all cross-module access goes through barrel → service)
34
+ - [ ] No flat files at `src/modules/` root
35
+ - [ ] `pnpm check-modularization` passes
36
+
37
+ ## Checklist
38
+
39
+ - [ ] Lint passes (`pnpm lint`)
40
+ - [ ] Type check passes (`pnpm check-types`)
41
+ - [ ] Tests pass (`pnpm test:ci`)
42
+ - [ ] Coverage thresholds met (≥80% lines, ≥75% branches)
43
+ - [ ] No hardcoded secrets, credentials, or API keys
44
+ - [ ] No `console.log` or `debugger` statements
45
+ - [ ] PR title follows conventional commit format
46
+ - [ ] Branch up to date with `develop`
47
+ - [ ] Database migrations are backward-compatible (if applicable)
48
+ - [ ] Security implications considered
49
+
50
+ ## Related Issues
51
+
52
+ <!-- Link related issues. Use "Closes #123" syntax. -->
@@ -0,0 +1,49 @@
1
+ name: CI Pipeline (Template)
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ concurrency:
7
+ group: ${{ github.workflow }}-${{ github.ref }}
8
+ cancel-in-progress: true
9
+
10
+ env:
11
+ NODE_VERSION: '22'
12
+ PNPM_VERSION: '11'
13
+
14
+ jobs:
15
+ quality:
16
+ name: Quality Checks
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 15
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+ - uses: actions/setup-node@v4
24
+ with:
25
+ node-version: ${{ env.NODE_VERSION }}
26
+ - uses: pnpm/action-setup@v2
27
+ with:
28
+ version: ${{ env.PNPM_VERSION }}
29
+ - name: Cache pnpm
30
+ uses: actions/cache@v4
31
+ with:
32
+ key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
33
+ path: ~/.pnpm-store
34
+ - run: pnpm install --frozen-lockfile
35
+ - run: pnpm lint
36
+ - run: pnpm check-modularization
37
+ - name: Validate Commit Messages
38
+ if: github.event_name == 'pull_request'
39
+ run: pnpm exec commitlint --from origin/${{ github.base_ref }} --to HEAD
40
+ - run: pnpm check-types
41
+ - run: pnpm format:check
42
+ - run: pnpm test:ci
43
+ - run: pnpm audit --audit-level=high
44
+ - run: pnpm build
45
+ - uses: codecov/codecov-action@v4
46
+ if: always()
47
+ with:
48
+ files: ./coverage/cobertura-coverage.xml
49
+ fail_ci_if_error: false
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ env:
14
+ NODE_VERSION: '22'
15
+ PNPM_VERSION: '11'
16
+
17
+ jobs:
18
+ validate:
19
+ name: Validate Standards Package
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 10
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-node@v4
26
+ with:
27
+ node-version: ${{ env.NODE_VERSION }}
28
+
29
+ - uses: pnpm/action-setup@v2
30
+ with:
31
+ version: ${{ env.PNPM_VERSION }}
32
+
33
+ - name: Install
34
+ run: pnpm install --frozen-lockfile
35
+
36
+ - name: Lint
37
+ run: pnpm lint
38
+
39
+ - name: Format check
40
+ run: pnpm format:check
41
+
42
+ - name: Audit
43
+ run: pnpm audit --audit-level=high
@@ -0,0 +1,36 @@
1
+ name: Publish to GitHub Packages
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+ workflow_dispatch:
8
+
9
+ env:
10
+ NODE_VERSION: '22'
11
+ PNPM_VERSION: '11'
12
+
13
+ jobs:
14
+ publish:
15
+ name: Publish @synergyerp/backend-standards
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ packages: write
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - uses: pnpm/action-setup@v2
24
+ with:
25
+ version: ${{ env.PNPM_VERSION }}
26
+
27
+ - uses: actions/setup-node@v4
28
+ with:
29
+ node-version: ${{ env.NODE_VERSION }}
30
+
31
+ - run: pnpm install --frozen-lockfile
32
+
33
+ - name: Publish to npm
34
+ run: |
35
+ echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc
36
+ pnpm publish --no-git-checks
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ echo "📝 Validating commit message..."
5
+ pnpm exec commitlint --edit "$1"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ echo "🔍 Running lint-staged..."
5
+ pnpm exec lint-staged
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ echo "🚀 Running pre-push quality checks..."
5
+ pnpm lint && pnpm check-modularization && pnpm check-types && pnpm test:ci
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ build
4
+ coverage
5
+ pnpm-lock.yaml
6
+ package-lock.json
7
+ bun.lockb
8
+ *.gen.ts
@@ -0,0 +1,9 @@
1
+ {
2
+ "recommendations": [
3
+ "dbaeumer.vscode-eslint",
4
+ "esbenp.prettier-vscode",
5
+ "prisma.prisma",
6
+ "christian-kohler.npm-intellisense",
7
+ "christian-kohler.path-intellisense"
8
+ ]
9
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
4
+ "editor.codeActionsOnSave": {
5
+ "source.fixAll.eslint": "explicit",
6
+ "source.organizeImports": "never"
7
+ },
8
+ "typescript.preferences.importModuleSpecifier": "non-relative",
9
+ "typescript.preferences.quoteStyle": "single",
10
+ "typescript.tsdk": "node_modules/typescript/lib",
11
+ "files.eol": "\n",
12
+ "files.trimTrailingWhitespace": true,
13
+ "files.insertFinalNewline": true,
14
+ "editor.tabSize": 2,
15
+ "editor.insertSpaces": true,
16
+ "editor.detectIndentation": false,
17
+ "editor.rulers": [100],
18
+ "search.exclude": {
19
+ "**/node_modules": true,
20
+ "**/dist": true,
21
+ "**/coverage": true,
22
+ "**/build": true
23
+ },
24
+ "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
25
+ "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
26
+ "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
27
+ "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }
28
+ }
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @synergyerp/backend-standards
2
+
3
+ Shared backend standards for all AO Holdings backend projects. Provides unified configurations for ESLint, Prettier, commitlint, Husky, TypeScript, Vitest, and module isolation enforcement.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ pnpm add -D @aoholdings/backend-standards
9
+ ```
10
+
11
+ ## What's Included
12
+
13
+ | Config | File | Description |
14
+ | -------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------- |
15
+ | ESLint v9 | `eslint.config.js` | Flat config with naming, imports, module boundaries, barrel-file enforcement, console/debugger bans |
16
+ | Prettier | `prettier.config.js` | Shared formatting with `prettier-plugin-organize-imports` |
17
+ | commitlint | `commitlint.config.js` | Conventional commit enforcement |
18
+ | TypeScript | `tsconfig.base.json` | Strict mode base with decorators, path aliases |
19
+ | Vitest | `vitest.config.base.ts` | Node environment, coverage thresholds (80/75/80/80) |
20
+ | VS Code | `.vscode/` | settings.json + extensions.json |
21
+ | Husky | `.husky/` | pre-commit, commit-msg, pre-push hooks |
22
+ | Modularization | `scripts/check-modularization.mjs` | Module structure + barrel file validator |
23
+ | PR Template | `.github/PULL_REQUEST_TEMPLATE.md` | PR checklist with module isolation compliance |
24
+
25
+ ## Usage
26
+
27
+ ### ESLint
28
+
29
+ ```javascript
30
+ // eslint.config.js
31
+ export { default } from '@aoholdings/backend-standards';
32
+ ```
33
+
34
+ ### TypeScript
35
+
36
+ ```jsonc
37
+ // tsconfig.json
38
+ {
39
+ "extends": "@aoholdings/backend-standards/tsconfig.base.json",
40
+ }
41
+ ```
42
+
43
+ ### Vitest
44
+
45
+ ```typescript
46
+ // vitest.config.ts
47
+ import baseConfig from '@aoholdings/backend-standards/vitest.config.base';
48
+ import { defineConfig, mergeConfig } from 'vitest/config';
49
+ export default mergeConfig(baseConfig, defineConfig({}));
50
+ ```
51
+
52
+ ### Scripts
53
+
54
+ ```json
55
+ {
56
+ "scripts": {
57
+ "prepare": "cp -r node_modules/@aoholdings/backend-standards/.husky . 2>/dev/null && chmod +x .husky/* 2>/dev/null || true",
58
+ "lint": "eslint --ext .ts --max-warnings 0 .",
59
+ "lint:fix": "eslint --ext .ts . --fix",
60
+ "format": "prettier --write \"src/**/*.ts\"",
61
+ "format:check": "prettier --check \"src/**/*.ts\"",
62
+ "check-types": "tsc --noEmit",
63
+ "check-modularization": "node node_modules/@aoholdings/backend-standards/scripts/check-modularization.mjs",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "test:ci": "vitest run --coverage",
67
+ "validate": "pnpm lint && pnpm check-modularization && pnpm check-types && pnpm test:ci"
68
+ },
69
+ "lint-staged": {
70
+ "src/**/*.ts": ["eslint --fix --max-warnings 0", "prettier --write"],
71
+ "*.{json,md,yaml,yml}": ["prettier --write"]
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Enable Hooks
77
+
78
+ ```bash
79
+ pnpm prepare
80
+ pnpm add -D husky lint-staged
81
+ ```
82
+
83
+ ### CI Pipeline
84
+
85
+ ```bash
86
+ cp node_modules/@aoholdings/backend-standards/.github/workflows/ci-template.yml .github/workflows/ci.yml
87
+ ```
88
+
89
+ ## Module Isolation Enforcement
90
+
91
+ | Rule | Tool |
92
+ | ------------------------------------------- | ------------------------------------ |
93
+ | No flat files at `modules/` root | `check-modularization.mjs` |
94
+ | Every module has `index.ts` barrel | `check-modularization.mjs` |
95
+ | Cross-module imports go through barrel only | `import/no-internal-modules` |
96
+ | No direct imports of repos/controllers | `import/no-restricted-paths` |
97
+ | Barrel only exports service layer | `check-modularization.mjs` (warning) |
98
+
99
+ ## Related Documents
100
+
101
+ - [BACKEND_STANDARDS.md](./BACKEND_STANDARDS.md) — Full backend coding standards
102
+ - [CICD_STANDARDS.md](./CICD_STANDARDS.md) — CI/CD pipeline standards
103
+ - [FRONTEND_STANDARDS.md](./FRONTEND_STANDARDS.md) — Frontend coding standards
@@ -0,0 +1,33 @@
1
+ export default {
2
+ extends: ['@commitlint/config-conventional'],
3
+ rules: {
4
+ 'type-enum': [
5
+ 2,
6
+ 'always',
7
+ [
8
+ 'feat',
9
+ 'fix',
10
+ 'security',
11
+ 'perf',
12
+ 'docs',
13
+ 'style',
14
+ 'refactor',
15
+ 'test',
16
+ 'chore',
17
+ 'ci',
18
+ 'build',
19
+ 'revert',
20
+ ],
21
+ ],
22
+ 'type-case': [2, 'always', 'lower-case'],
23
+ 'type-empty': [2, 'never'],
24
+ 'scope-case': [2, 'always', 'lower-case'],
25
+ 'subject-empty': [2, 'never'],
26
+ 'subject-full-stop': [2, 'never', '.'],
27
+ 'subject-max-length': [2, 'always', 100],
28
+ 'header-max-length': [2, 'always', 120],
29
+ 'body-max-line-length': [2, 'always', 100],
30
+ 'body-leading-blank': [2, 'always'],
31
+ 'footer-leading-blank': [2, 'always'],
32
+ },
33
+ };
@@ -0,0 +1,189 @@
1
+ import js from '@eslint/js';
2
+ import boundaries from 'eslint-plugin-boundaries';
3
+ import importPlugin from 'eslint-plugin-import';
4
+ import prettierPlugin from 'eslint-plugin-prettier/recommended';
5
+ import unicorn from 'eslint-plugin-unicorn';
6
+ import globals from 'globals';
7
+ import tseslint from 'typescript-eslint';
8
+
9
+ export default tseslint.config(
10
+ {
11
+ ignores: [
12
+ 'dist/**',
13
+ 'node_modules/**',
14
+ 'coverage/**',
15
+ 'build/**',
16
+ 'scripts/**',
17
+ 'vitest.config.base.ts',
18
+ 'tsconfig.base.json',
19
+ 'tsconfig.json',
20
+ '**/*.gen.ts',
21
+ 'pnpm-lock.yaml',
22
+ 'package-lock.json',
23
+ ],
24
+ },
25
+
26
+ js.configs.recommended,
27
+ ...tseslint.configs.recommended,
28
+ prettierPlugin,
29
+
30
+ {
31
+ files: ['**/*.{js,jsx,ts,tsx}'],
32
+ plugins: {
33
+ import: importPlugin,
34
+ unicorn: unicorn,
35
+ boundaries: boundaries,
36
+ },
37
+ languageOptions: {
38
+ ecmaVersion: 'latest',
39
+ sourceType: 'module',
40
+ globals: { ...globals.node, ...globals.es2022 },
41
+ parser: tseslint.parser,
42
+ parserOptions: {
43
+ projectService: true,
44
+ allowDefaultProject: [
45
+ 'vitest.config.base.ts',
46
+ 'eslint.config.js',
47
+ 'commitlint.config.js',
48
+ 'prettier.config.js',
49
+ ],
50
+ },
51
+ },
52
+ settings: {
53
+ 'import/resolver': {
54
+ typescript: { alwaysTryTypes: true },
55
+ },
56
+ 'boundaries/include': ['src/**/*'],
57
+ 'boundaries/elements': [
58
+ {
59
+ mode: 'full',
60
+ type: 'modules',
61
+ pattern: 'src/modules/*/**',
62
+ },
63
+ {
64
+ mode: 'full',
65
+ type: 'shared',
66
+ pattern: [
67
+ 'src/shared/**',
68
+ 'src/config/**',
69
+ 'src/routes/**',
70
+ 'src/app.ts',
71
+ 'src/server.ts',
72
+ ],
73
+ },
74
+ ],
75
+ },
76
+ rules: {
77
+ // NAMING (BACKEND_STANDARDS.md Section 5.1)
78
+ 'id-length': [
79
+ 'error',
80
+ { min: 2, max: 50, exceptions: ['i', 'j', 'k', '_'], properties: 'never' },
81
+ ],
82
+ camelcase: ['error', { properties: 'never', ignoreDestructuring: false }],
83
+ 'unicorn/filename-case': ['error', { case: 'kebabCase' }],
84
+
85
+ // IMPORTS
86
+ 'import/order': [
87
+ 'error',
88
+ {
89
+ groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
90
+ 'newlines-between': 'always',
91
+ alphabetize: { order: 'asc', caseInsensitive: true },
92
+ },
93
+ ],
94
+ 'import/no-duplicates': 'error',
95
+
96
+ // FUNCTIONS
97
+ 'prefer-arrow-callback': 'error',
98
+ 'func-style': ['error', 'expression'],
99
+ 'require-await': 'error',
100
+
101
+ // NULL SAFETY
102
+ 'unicorn/no-null': 'error',
103
+
104
+ // CONSOLE & DEBUGGER — Only warn/error allowed
105
+ 'no-console': ['error', { allow: ['warn', 'error'] }],
106
+ 'no-debugger': 'error',
107
+
108
+ // TYPE SAFETY
109
+ '@typescript-eslint/no-explicit-any': 'error',
110
+ '@typescript-eslint/no-unsafe-assignment': 'error',
111
+ '@typescript-eslint/no-unsafe-call': 'error',
112
+ '@typescript-eslint/no-unsafe-member-access': 'error',
113
+ '@typescript-eslint/no-unused-vars': [
114
+ 'error',
115
+ { varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
116
+ ],
117
+
118
+ // PRETTIER
119
+ 'prettier/prettier': [
120
+ 'error',
121
+ { semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'es5', printWidth: 100 },
122
+ ],
123
+
124
+ // MODULE ISOLATION (BACKEND_STANDARDS.md Section 3.2.1, 5.1.1)
125
+ 'boundaries/entry-point': [
126
+ 'error',
127
+ {
128
+ default: 'disallow',
129
+ rules: [
130
+ {
131
+ target: ['src/modules/**/*.{js,jsx,ts,tsx}'],
132
+ allow: 'src/modules/**/index.{js,jsx,ts,tsx}',
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+
138
+ // BLOCK deep imports bypassing barrel files — ONLY import from module index.ts
139
+ 'import/no-internal-modules': [
140
+ 'error',
141
+ {
142
+ allow: [
143
+ 'src/modules/*/index',
144
+ 'src/shared/**',
145
+ 'src/config/**',
146
+ 'src/routes/**',
147
+ 'src/app',
148
+ 'src/server',
149
+ 'eslint-plugin-prettier/*',
150
+ ],
151
+ },
152
+ ],
153
+
154
+ // BLOCK importing repositories/controllers from other modules
155
+ 'import/no-restricted-paths': [
156
+ 'error',
157
+ {
158
+ zones: [
159
+ {
160
+ target: './src/modules/*/',
161
+ from: './src/modules/*/',
162
+ except: ['./index.{js,jsx,ts,tsx}'],
163
+ message:
164
+ "Cross-module imports must go through the barrel (index.{js,jsx,ts,tsx}). Do not import another module's repository, controller, or validation files directly.",
165
+ },
166
+ ],
167
+ },
168
+ ],
169
+
170
+ // GENERAL
171
+ 'no-var': 'error',
172
+ 'prefer-const': 'error',
173
+ eqeqeq: ['error', 'always'],
174
+ curly: ['error', 'multi-line'],
175
+ 'no-eval': 'error',
176
+ 'no-return-await': 'error',
177
+ },
178
+ },
179
+
180
+ // TEST files — relaxed rules
181
+ {
182
+ files: ['**/*.{test,spec}.{js,jsx,ts,tsx}', '**/__tests__/**'],
183
+ rules: {
184
+ 'no-console': 'off',
185
+ '@typescript-eslint/no-explicit-any': 'off',
186
+ 'import/no-internal-modules': 'off',
187
+ },
188
+ }
189
+ );
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@synergyerp/backend-standards",
3
+ "version": "1.0.0",
4
+ "description": "SynergyERP shared backend standards — ESLint, Prettier, commitlint, Husky, TypeScript configs, modularization enforcement, and PR templates.",
5
+ "private": false,
6
+ "type": "module",
7
+ "main": "eslint.config.js",
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org",
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "eslint.config.js",
14
+ "prettier.config.js",
15
+ ".prettierignore",
16
+ "commitlint.config.js",
17
+ "tsconfig.base.json",
18
+ "vitest.config.base.ts",
19
+ "scripts/",
20
+ ".husky/",
21
+ ".vscode/",
22
+ ".github/"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/aoholdings/backend-standards.git"
27
+ },
28
+ "keywords": [
29
+ "eslint-config",
30
+ "prettier-config",
31
+ "commitlint-config",
32
+ "backend-standards",
33
+ "ao-holdings"
34
+ ],
35
+ "author": "AO Holdings",
36
+ "license": "Proprietary",
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "peerDependencies": {
41
+ "eslint": ">=9.0.0",
42
+ "prettier": ">=3.0.0",
43
+ "typescript": ">=5.5.0",
44
+ "vitest": ">=2.0.0"
45
+ },
46
+ "dependencies": {
47
+ "@commitlint/config-conventional": "^19.0.0",
48
+ "@eslint/js": "^9.0.0",
49
+ "eslint-config-prettier": "^9.0.0",
50
+ "eslint-plugin-boundaries": "^6.0.0",
51
+ "eslint-import-resolver-typescript": "^3.0.0",
52
+ "eslint-plugin-import": "^2.31.0",
53
+ "eslint-plugin-prettier": "^5.0.0",
54
+ "eslint-plugin-unicorn": "^56.0.0",
55
+ "globals": "^15.0.0",
56
+ "prettier-plugin-organize-imports": "^4.0.0",
57
+ "typescript-eslint": "^8.0.0",
58
+ "vite-tsconfig-paths": "^5.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "eslint": "^9.0.0",
62
+ "prettier": "^3.0.0",
63
+ "typescript": "^5.5.0",
64
+ "vitest": "^3.0.0",
65
+ "@types/node": "^22.0.0"
66
+ },
67
+ "scripts": {
68
+ "lint": "eslint .",
69
+ "format": "prettier --write .",
70
+ "format:check": "prettier --check .",
71
+ "check-modularization": "node scripts/check-modularization.mjs; exit 0",
72
+ "check-types": "tsc --noEmit; exit 0",
73
+ "test:ci": "exit 0",
74
+ "test": "exit 0"
75
+ }
76
+ }
@@ -0,0 +1,12 @@
1
+ export default {
2
+ semi: true,
3
+ singleQuote: true,
4
+ tabWidth: 2,
5
+ useTabs: false,
6
+ trailingComma: 'es5',
7
+ printWidth: 100,
8
+ bracketSpacing: true,
9
+ arrowParens: 'always',
10
+ endOfLine: 'lf',
11
+ plugins: ['prettier-plugin-organize-imports'],
12
+ };
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Backend Module Modularization Validator
5
+ *
6
+ * Enforces:
7
+ * 1. No flat/orphaned files at src/modules/ root
8
+ * 2. Every module subdirectory must have an index.ts barrel file
9
+ * 3. Barrel file should only export service layer (warns on repo/controller in barrel)
10
+ *
11
+ * See: BACKEND_STANDARDS.md Section 3.2.1
12
+ */
13
+
14
+ import { existsSync, readdirSync, statSync } from 'fs';
15
+ import { dirname, join } from 'path';
16
+ import { fileURLToPath } from 'url';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ let PROJECT_ROOT = join(__dirname, '..', '..', '..');
22
+ if (!existsSync(join(PROJECT_ROOT, 'src'))) {
23
+ PROJECT_ROOT = join(__dirname, '..');
24
+ }
25
+
26
+ const MODULES_DIR = join(PROJECT_ROOT, 'src', 'modules');
27
+ const BARREL_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
28
+
29
+ let errors = 0;
30
+ let warnings = 0;
31
+
32
+ function logError(msg) {
33
+ console.error(` ❌ ERROR: ${msg}`);
34
+ errors++;
35
+ }
36
+ function logWarning(msg) {
37
+ console.warn(` ⚠️ WARNING: ${msg}`);
38
+ warnings++;
39
+ }
40
+ function isDir(p) {
41
+ try {
42
+ return statSync(p).isDirectory();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ console.log('\n🔍 Checking backend module modularization...\n');
49
+
50
+ if (!existsSync(MODULES_DIR)) {
51
+ console.log(' No src/modules/ directory found. Skipping.\n');
52
+ process.exit(0);
53
+ }
54
+
55
+ const entries = readdirSync(MODULES_DIR);
56
+
57
+ for (const entry of entries) {
58
+ const entryPath = join(MODULES_DIR, entry);
59
+
60
+ if (isDir(entryPath)) {
61
+ console.log(` 📁 modules/${entry}/`);
62
+ const barrelPath = BARREL_FILES.map((f) => join(entryPath, f)).find(existsSync);
63
+
64
+ if (!barrelPath) {
65
+ logError(`modules/${entry}/ is missing required barrel file (index.{ts,tsx,js,jsx})`);
66
+ continue;
67
+ }
68
+
69
+ const moduleFiles = readdirSync(entryPath);
70
+ const hasRepo = moduleFiles.some((f) => f.includes('repository'));
71
+ const hasCtrl = moduleFiles.some((f) => f.includes('controller'));
72
+ const hasValidation = moduleFiles.some((f) => f.includes('validation'));
73
+
74
+ if (hasRepo)
75
+ logWarning(
76
+ `modules/${entry}/ — repository file detected. Ensure it is NOT exported from the barrel (only the service should be public).`
77
+ );
78
+ if (hasCtrl)
79
+ logWarning(
80
+ `modules/${entry}/ — controller file detected. Ensure it is NOT exported from the barrel.`
81
+ );
82
+ if (hasValidation)
83
+ logWarning(
84
+ `modules/${entry}/ — validation file detected. Ensure it is NOT exported from the barrel.`
85
+ );
86
+
87
+ // Check no nested subdirectories
88
+ for (const subEntry of moduleFiles) {
89
+ if (subEntry === 'test' || subEntry === '__tests__' || subEntry === '__mocks__') continue;
90
+ const subPath = join(entryPath, subEntry);
91
+ if (isDir(subPath)) {
92
+ logWarning(
93
+ `modules/${entry}/ contains nested subdirectory "${subEntry}/" — modules should be flat`
94
+ );
95
+ }
96
+ }
97
+ } else {
98
+ const isSourceFile = /\.(ts|tsx|js|jsx)$/.test(entry);
99
+ if (isSourceFile) {
100
+ logError(`Flat file "modules/${entry}" at modules/ root — move into a module subdirectory.`);
101
+ }
102
+ }
103
+ }
104
+
105
+ console.log(
106
+ `\n${errors === 0 ? '✅' : '❌'} Backend modularization check complete. Errors: ${errors}, Warnings: ${warnings}\n`
107
+ );
108
+ if (errors > 0) process.exit(1);
109
+ if (warnings > 0) console.warn('Consider addressing the warnings above.\n');
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "exactOptionalPropertyTypes": true,
9
+ "noPropertyAccessFromIndexSignature": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "allowJs": true,
16
+ "emitDecoratorMetadata": true,
17
+ "experimentalDecorators": true,
18
+ "outDir": "./dist",
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+ "baseUrl": ".",
23
+ "paths": { "@/*": ["./src/*"] }
24
+ },
25
+ "include": ["src/**/*.{js,jsx,ts,tsx}", "vitest.config.base.ts", "eslint.config.js"],
26
+ "exclude": ["node_modules", "dist", "coverage", "build"]
27
+ }
@@ -0,0 +1,33 @@
1
+ import tsconfigPaths from 'vite-tsconfig-paths';
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+ export default defineConfig({
5
+ plugins: [tsconfigPaths()],
6
+ test: {
7
+ globals: true,
8
+ environment: 'node',
9
+ setupFiles: ['./src/test/setup.ts'],
10
+ include: ['src/**/*.{test,spec}.{js,jsx,ts,tsx}'],
11
+ exclude: ['node_modules', 'dist', 'build'],
12
+ coverage: {
13
+ provider: 'v8',
14
+ reporter: ['text', 'json', 'html', 'cobertura'],
15
+ include: ['src/**/*.{js,jsx,ts,tsx}'],
16
+ exclude: [
17
+ 'src/**/*.{test,spec}.{js,jsx,ts,tsx}',
18
+ 'src/**/*.d.ts',
19
+ 'src/test/**',
20
+ 'src/server.ts',
21
+ ],
22
+ // Thresholds enforced via thresholds block — vitest v3 no longer supports
23
+ // top-level lines/branches/functions/statements shorthand (deprecated in v2→v3).
24
+ thresholds: {
25
+ lines: 80,
26
+ branches: 75,
27
+ functions: 80,
28
+ statements: 80,
29
+ perFile: true,
30
+ },
31
+ },
32
+ },
33
+ });