refactor(server): migrate app entry point to TypeScript (.cts) — server migration 100%

Completes the JS→TS server migration. server.js → server.cts (Node
type-stripping, no build step), and every launch/config reference now
points at it: package.json (main/dev:api/start/check:server) and the
Docker CMD.

scripts/seedDemoData.js is required at runtime by routes/user.cts, so it
crosses into the server runtime — migrated to .cts and its require of
db/database updated to the .cts path. The remaining scripts/*.js are
standalone dev/ops tooling (outside the runtime and the check:server
boundary); their stale extensionless requires of now-.cts modules were
repaired so they still run, and the PM2 ecosystem config entry point was
updated to server.cts.

Verified: 0 runtime .js remain in the server tree; typecheck:server and
check:server clean; 226/226 tests pass; `node server.cts` boots and
/api/health returns 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-06 11:41:56 -05:00
parent 135bd98c6a
commit 20d2e8965b
10 changed files with 21 additions and 20 deletions

View File

@ -51,4 +51,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD node -e "require('http').get('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server.js"]
CMD ["node", "server.cts"]

View File

@ -2,16 +2,16 @@
"name": "bill-tracker",
"version": "0.40.0",
"description": "Monthly bill tracking system",
"main": "server.js",
"main": "server.cts",
"engines": {
"node": ">=22.18.0"
},
"scripts": {
"dev:api": "node --watch server.js",
"dev:api": "node --watch server.cts",
"dev:ui": "vite",
"dev": "concurrently \"npm run dev:api\" \"npm run dev:ui\"",
"build": "vite build",
"check:server": "find server.js db middleware routes services utils workers \\( -name '*.js' -o -name '*.mts' -o -name '*.cts' -o -name '*.ts' \\) -print0 | xargs -0 -n1 node --check",
"check:server": "find server.cts db middleware routes services utils workers \\( -name '*.js' -o -name '*.mts' -o -name '*.cts' -o -name '*.ts' \\) -print0 | xargs -0 -n1 node --check",
"typecheck:server": "tsc --noEmit -p tsconfig.server.json",
"lint": "eslint client",
"typecheck": "tsc --noEmit -p tsconfig.json",
@ -25,7 +25,7 @@
"test:e2e:probe": "node e2e/setup/prepare-db.js && playwright test --project=probe",
"smoke:prod": "bash scripts/prod-smoke.sh",
"ci": "npm run lint && npm run typecheck && npm run typecheck:server && npm run check:server && npm run test:all && npm run build",
"start": "node server.js"
"start": "node server.cts"
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.2",

View File

@ -5,7 +5,7 @@ import type { Req, Res, Next } from '../types/http';
const express = require('express');
const router = express.Router();
const { getDb } = require('../db/database.cts');
const { seedDemoData } = require('../scripts/seedDemoData');
const { seedDemoData } = require('../scripts/seedDemoData.cts');
const { demoDataLimiter } = require('../middleware/rateLimiter.cts');
const { eraseUserData } = require('../services/userDataService.cts');
const { standardizeError } = require('../middleware/errorFormatter.cts');

View File

@ -7,7 +7,7 @@ module.exports = {
apps: [
{
name: 'bill-tracker',
script: 'server.js',
script: 'server.cts',
cwd: '/opt/bill-tracker',
instances: 1,
exec_mode: 'fork',

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
'use strict';
const { closeDb, getDb, getDbPath } = require('../db/database');
const { closeDb, getDb, getDbPath } = require('../db/database.cts');
try {
getDb();

View File

@ -11,7 +11,7 @@ const path = require('path');
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'db', 'bills.db');
// Import database helper
const { getDb, ensureUserDefaultCategories } = require('../db/database');
const { getDb, ensureUserDefaultCategories } = require('../db/database.cts');
// Money columns (expected_amount, current_balance, minimum_payment) are stored as
// integer cents since migration v1.03 — convert the demo dollars before insert.
const { toCents } = require('../utils/money.mts');

View File

@ -2,7 +2,7 @@
'use strict';
const assert = require('assert');
const { cookieOpts } = require('../services/authService');
const { cookieOpts } = require('../services/authService.cts');
function withEnv(values, fn) {
const saved = {};

View File

@ -18,7 +18,7 @@ const {
resolveYearMonth,
findBillMatches: serviceFindBillMatches,
buildRecommendation: serviceBuildRecommendation,
} = require('../services/spreadsheetImportService');
} = require('../services/spreadsheetImportService.cts');
// ─── Re-implement helpers for row-level tests (avoids DB init) ────────────────
@ -517,8 +517,8 @@ function runApplyRegression(name, body) {
const APPLY_SETUP = `
const assert = require('assert');
const xlsx = require('xlsx');
const { getDb, ensureUserDefaultCategories } = require('./db/database');
const { previewSpreadsheet, applyImportDecisions } = require('./services/spreadsheetImportService');
const { getDb, ensureUserDefaultCategories } = require('./db/database.cts');
const { previewSpreadsheet, applyImportDecisions } = require('./services/spreadsheetImportService.cts');
async function setup() {
const db = getDb();
db.prepare('INSERT INTO users (username,password_hash) VALUES (?,?)').run('importer','x');
@ -615,8 +615,8 @@ ${APPLY_SETUP}
runApplyRegression('create_new_bill imports related paid months for same detected bill', `
const assert = require('assert');
const xlsx = require('xlsx');
const { getDb } = require('./db/database');
const { previewSpreadsheet, applyImportDecisions } = require('./services/spreadsheetImportService');
const { getDb } = require('./db/database.cts');
const { previewSpreadsheet, applyImportDecisions } = require('./services/spreadsheetImportService.cts');
(async () => {
const db = getDb();
db.prepare('INSERT INTO users (username,password_hash) VALUES (?,?)').run('importer','x');
@ -857,8 +857,8 @@ const assert = require('assert');
const fs = require('fs');
const path = require('path');
const Database = require('better-sqlite3');
const { getDb, ensureUserDefaultCategories } = require('./db/database');
const { previewUserDbImport, applyUserDbImport } = require('./services/userDbImportService');
const { getDb, ensureUserDefaultCategories } = require('./db/database.cts');
const { previewUserDbImport, applyUserDbImport } = require('./services/userDbImportService.cts');
function createExportBuffer(nameSuffix = '') {
const file = path.join('/tmp', 'bill-tracker-user-export-test-' + process.pid + '-' + Date.now() + nameSuffix + '.sqlite');

View File

@ -48,8 +48,8 @@ const path = require('path');
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bill-tracker-oidc-'));
process.env.DB_PATH = path.join(tempDir, 'test.sqlite');
const { getDb, getSetting, setSetting, closeDb } = require('../db/database');
const oidcService = require('../services/oidcService');
const { getDb, getSetting, setSetting, closeDb } = require('../db/database.cts');
const oidcService = require('../services/oidcService.cts');
function clearOidcEnv() {
for (const key of [
@ -467,7 +467,7 @@ test('admin role only granted by configured group', async () => {
});
test('OIDC-created user cannot local-password login', async () => {
const { login } = require('../services/authService');
const { login } = require('../services/authService.cts');
clearOidcEnv();
resetOidcSettings();
await oidcService.findOrProvisionUser({

View File

@ -1,3 +1,4 @@
// @ts-nocheck — app entry/bootstrap converted to .cts (Node type-stripping); Express wiring, typing deferred.
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');