Skip to content

Commit 4e9441b

Browse files
authored
Merge pull request #480 from lettalkdata-cloud/fix/issues-463-466-zod-types-ci
Fix/issues 463 466 zod types ci
2 parents 9d8832f + 95221fe commit 4e9441b

13 files changed

Lines changed: 263 additions & 85 deletions

.github/workflows/typecheck.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Type Check
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
typecheck:
13+
name: TypeScript type check & dependency audit
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: '20'
24+
cache: 'npm'
25+
26+
- name: Clean install dependencies
27+
run: npm ci
28+
29+
- name: Check for undeclared dependencies
30+
run: npm run check:deps
31+
32+
- name: Run TypeScript type check
33+
run: npm run typecheck
34+
35+
- name: Build
36+
run: npm run build

AGENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ Prefer domain events over direct service calls. Never import a sibling controlle
8181
- [ ] Swagger updated · `npm run build` passes · `npm test` passes
8282
- [ ] **AGENTS.md reviewed if conventions, boundaries, or module structure changed**
8383

84+
### Clean-Install Build Triage (hard rule)
85+
86+
Build triage must be based strictly on `package.json`, `package-lock.json`, and source files.
87+
88+
- **Never** assume a package is available because it exists locally — CI runs `npm ci` from scratch.
89+
- If a new runtime import is added, it **must** go in `dependencies` (not `devDependencies`).
90+
- Run `npm run check:deps` before committing to catch undeclared imports.
91+
- To reproduce CI locally: `rm -rf node_modules && npm ci && npm run build`
92+
- CI pins Node.js 20 via `.github/workflows/typecheck.yml` and always uses `npm ci`.
93+
8494
## 9. Token Efficiency
8595
Read before you write. Batch parallel reads. Cite `file:line`. Prefer small diffs. Skip filler prose.
8696

CONTRIBUTING.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,30 @@ If you're stuck, have questions, or want to discuss ideas before starting:
197197

198198
Thank you for contributing to Navin-backend!
199199
Together, we're building a transparent and secure delivery tracking platform on Stellar.
200+
201+
---
202+
203+
## Build & Test Requirements
204+
205+
All PRs must pass the following CI checks before they can be merged:
206+
207+
| Check | Command | Description |
208+
|-------|---------|-------------|
209+
| Dependency audit | `npm run check:deps` | Scans `src/` imports against `package.json` `dependencies` |
210+
| Type check | `npm run typecheck` | Runs `tsc --noEmit` with strict mode — zero errors required |
211+
| Build | `npm run build` | Compiles TypeScript to `dist/` — must succeed cleanly |
212+
| Tests | `npm test` | Full test suite must pass |
213+
214+
### Clean-Install Build Triage
215+
216+
**All build and type-check work must be reproducible in a clean `npm ci` environment.** This means:
217+
218+
- Base your triage strictly on `package.json`, `package-lock.json`, and source files in `src/`.
219+
- **Never** assume a package is available because it exists in your local `node_modules/`. Local installs may include packages that are not in `package.json` (transitive deps, global installs, or manually added packages).
220+
- If a build fails in CI but passes locally, reproduce the clean-install environment before debugging:
221+
```bash
222+
rm -rf node_modules && npm ci && npm run build
223+
```
224+
- If you add a new runtime import, add the package to `package.json` `dependencies` (**not** `devDependencies`) and commit the updated `package-lock.json`.
225+
- Run `npm run check:deps` locally before opening a PR to catch undeclared imports early.
226+
- CI pins Node.js 20 and uses `npm ci` — never `npm install` — to guarantee a reproducible dependency tree.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Navin Backend
22

3+
[![Type Check](https://github.com/Navin-xmr/navin-backend/actions/workflows/typecheck.yml/badge.svg)](https://github.com/Navin-xmr/navin-backend/actions/workflows/typecheck.yml)
4+
35
**Navin** is a blockchain-powered logistics platform that improves supply chain visibility for enterprises through tokenized shipments, immutable milestone tracking, and automated settlements.
46
By creating a zero-trust interface between logistics providers and their clients, Navin aims to ensure both parties access identical real-time data — removing information asymmetry and enabling seamless, dispute-free operations.
57

@@ -480,6 +482,7 @@ API_KEY_PREFIX=sk_
480482
| `npm run build` | Compile TypeScript to JavaScript in `dist/` |
481483
| `npm run start` | Run production build |
482484
| `npm run typecheck` | Run TypeScript type checker |
485+
| `npm run check:deps` | Check runtime imports against `package.json` dependencies |
483486
| `npm run lint` | Run ESLint and Prettier checks |
484487
| `npm run lint:fix` | Fix linting and formatting issues |
485488
| `npm test` | Run test suite (Jest) |

package-lock.json

Lines changed: 0 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"build": "tsc -p tsconfig.json",
88
"start": "node dist/src/main.js",
99
"typecheck": "tsc -p tsconfig.json --noEmit",
10+
"check:deps": "node scripts/check-undeclared-deps.js",
1011
"lint": "eslint src --ext .ts",
1112
"lint:fix": "eslint src --ext .ts --fix",
1213
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",

scripts/check-undeclared-deps.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
/**
3+
* check-undeclared-deps.js
4+
*
5+
* Scans all runtime imports in src/ and verifies every bare-specifier package
6+
* is listed in package.json `dependencies`. Reports any packages that are
7+
* imported but not declared — catching issues like an accidentally-removed
8+
* entry or a package that only exists in a contributor's local node_modules.
9+
*
10+
* Usage:
11+
* node scripts/check-undeclared-deps.js
12+
* npm run check:deps
13+
*
14+
* Exit codes:
15+
* 0 — all imports are declared
16+
* 1 — one or more undeclared imports found
17+
*/
18+
19+
import { readFileSync, readdirSync, statSync } from 'fs';
20+
import { join, extname } from 'path';
21+
import { fileURLToPath } from 'url';
22+
import { createRequire } from 'module';
23+
24+
const require = createRequire(import.meta.url);
25+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
26+
const ROOT = join(__dirname, '..');
27+
const SRC_DIR = join(ROOT, 'src');
28+
const PKG_PATH = join(ROOT, 'package.json');
29+
30+
// ── Load declared dependencies ────────────────────────────────────────────────
31+
const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8'));
32+
const declared = new Set([
33+
...Object.keys(pkg.dependencies ?? {}),
34+
// devDependencies intentionally excluded — runtime imports must be in dependencies
35+
]);
36+
37+
// Node.js built-in modules — never need to be in package.json
38+
const BUILTINS = new Set([
39+
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
40+
'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
41+
'events', 'fs', 'http', 'http2', 'https', 'inspector', 'module', 'net',
42+
'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring',
43+
'readline', 'repl', 'stream', 'string_decoder', 'sys', 'timers',
44+
'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads',
45+
'zlib',
46+
]);
47+
48+
// ── Collect all .ts source files under src/ ───────────────────────────────────
49+
function collectFiles(dir, results = []) {
50+
for (const entry of readdirSync(dir)) {
51+
const full = join(dir, entry);
52+
const stat = statSync(full);
53+
if (stat.isDirectory()) {
54+
collectFiles(full, results);
55+
} else if (['.ts', '.tsx'].includes(extname(entry))) {
56+
results.push(full);
57+
}
58+
}
59+
return results;
60+
}
61+
62+
// ── Extract bare-specifier imports from source text ───────────────────────────
63+
// Matches:
64+
// import ... from 'pkg'
65+
// import ... from "pkg"
66+
// import('pkg')
67+
// require('pkg')
68+
// export ... from 'pkg'
69+
const IMPORT_RE =
70+
/(?:import|export)\s+(?:type\s+)?(?:[^'"]*from\s+)?['"]([^'"]+)['"]/g;
71+
const DYNAMIC_RE = /(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
72+
73+
function extractPackageNames(source) {
74+
const names = new Set();
75+
for (const re of [IMPORT_RE, DYNAMIC_RE]) {
76+
re.lastIndex = 0;
77+
let match;
78+
while ((match = re.exec(source)) !== null) {
79+
const specifier = match[1];
80+
// Relative and absolute paths are not packages
81+
if (specifier.startsWith('.') || specifier.startsWith('/')) continue;
82+
// node: protocol builtins
83+
if (specifier.startsWith('node:')) continue;
84+
// Scoped package: @scope/name → root is @scope/name
85+
// Un-scoped package: pkg/sub/path → root is pkg
86+
const root = specifier.startsWith('@')
87+
? specifier.split('/').slice(0, 2).join('/')
88+
: specifier.split('/')[0];
89+
names.add(root);
90+
}
91+
}
92+
return names;
93+
}
94+
95+
// ── Main ──────────────────────────────────────────────────────────────────────
96+
const files = collectFiles(SRC_DIR);
97+
const undeclared = new Set();
98+
99+
for (const file of files) {
100+
const source = readFileSync(file, 'utf8');
101+
for (const pkg of extractPackageNames(source)) {
102+
if (!BUILTINS.has(pkg) && !declared.has(pkg)) {
103+
undeclared.add(pkg);
104+
console.error(` [UNDECLARED] "${pkg}" — imported in ${file.replace(ROOT + '/', '')}`);
105+
}
106+
}
107+
}
108+
109+
if (undeclared.size > 0) {
110+
console.error(
111+
`\n✖ ${undeclared.size} undeclared package(s) found. Add them to package.json "dependencies" and re-run npm ci.\n`
112+
);
113+
process.exit(1);
114+
}
115+
116+
console.log(`✔ All imports match package.json dependencies (${files.length} files scanned).`);

src/modules/auth/auth.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
resetPasswordController,
2323
refreshController,
2424
registerCompanyController,
25-
setup2faController,
2625
} from './auth.controller.js';
2726
import {
2827
createApiKeyController,
@@ -132,6 +131,7 @@ authRouter.post(
132131
'/2fa/backup-codes/regenerate',
133132
asyncHandler(requireAuth),
134133
asyncHandler(regenerateBackupCodesController)
134+
);
135135
// Session management routes (protected by JWT auth)
136136
authRouter.get('/sessions', asyncHandler(requireAuth), asyncHandler(listSessionsController));
137137
authRouter.delete(

src/modules/auth/twoFactor.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import bcrypt from 'bcrypt';
22
import { randomBytes } from 'crypto';
3-
import { authenticator } from 'otplib';
3+
import { generateSecret, generateURI, verifySync } from 'otplib';
44
import { AppError, ErrorCodes } from '../../shared/http/errors.js';
55
import { UserModel } from '../users/users.model.js';
66

@@ -52,12 +52,12 @@ export async function setup2fa(userId: string): Promise<{ otpauthUrl: string; se
5252
throw new AppError(409, '2FA is already enabled', ErrorCodes.TOTP_ALREADY_ENABLED);
5353
}
5454

55-
const secret = authenticator.generateSecret();
55+
const secret = generateSecret();
5656

5757
// Store the pending secret (not yet active — totpEnabled stays false)
5858
await UserModel.findByIdAndUpdate(userId, { totpSecret: secret });
5959

60-
const otpauthUrl = authenticator.keyuri(user.email as string, 'Navin', secret);
60+
const otpauthUrl = generateURI({ label: user.email as string, issuer: 'Navin', secret });
6161

6262
return { otpauthUrl, secret };
6363
}
@@ -96,8 +96,8 @@ export async function verify2fa(userId: string, code: string): Promise<{ backupC
9696
throw new AppError(409, '2FA is already enabled', ErrorCodes.TOTP_ALREADY_ENABLED);
9797
}
9898

99-
const isValid = authenticator.verify({ token: code, secret: user.totpSecret as string });
100-
if (!isValid) {
99+
const verifyResult = verifySync({ token: code, secret: user.totpSecret as string });
100+
if (!verifyResult.valid) {
101101
throw new AppError(400, 'Invalid TOTP code', ErrorCodes.TOTP_INVALID_CODE);
102102
}
103103

0 commit comments

Comments
 (0)