From 37c2688fb55778d9935767b056c46b267b96d9ea Mon Sep 17 00:00:00 2001 From: simonredfern Date: Thu, 11 Dec 2025 20:44:07 +0100 Subject: [PATCH] ES modules --- package.json | 6 +- scripts/build-and-test-production.sh | 216 ++++++++++++++++++ server/app.ts | 35 ++- .../controllers/OAuth2CallbackController.ts | 4 +- server/controllers/OAuth2ConnectController.ts | 4 +- server/controllers/OpeyIIController.ts | 10 +- server/controllers/RequestController.ts | 4 +- server/controllers/StatusController.ts | 8 +- server/controllers/UserController.ts | 8 +- .../OAuth2AuthorizationMiddleware.ts | 6 +- .../middlewares/OAuth2CallbackMiddleware.ts | 6 +- server/services/OBPClientService.ts | 2 +- server/services/OBPConsentsService.ts | 4 +- server/services/OpeyClientService.ts | 4 +- server/test/OBPClientService.test.ts | 2 +- server/test/OBPConsentsService.test.ts | 4 +- server/test/OpeyClientService.test.ts | 4 +- server/test/opey-controller.test.ts | 8 +- server/test/opey.test.ts | 4 +- tsconfig.server.json | 9 +- 20 files changed, 296 insertions(+), 52 deletions(-) create mode 100755 scripts/build-and-test-production.sh diff --git a/package.json b/package.json index 53eb34a..9d38883 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,18 @@ { "name": "api-explorer", "version": "1.1.3", + "type": "module", "private": true, "types": [ "jest" ], "scripts": { "dev": "vite & tsx --tsconfig tsconfig.server.json server/app.ts", - "build": "run-p build-only", + "build": "run-p build-only build-server", "build-server": "tsc --project tsconfig.server.json", + "build-production": "npm run build", + "test-production": "./scripts/build-and-test-production.sh", + "start": "node dist-server/server/app.js", "preview": "vite preview", "test": "vitest", "build-only": "vite build", diff --git a/scripts/build-and-test-production.sh b/scripts/build-and-test-production.sh new file mode 100755 index 0000000..3bec357 --- /dev/null +++ b/scripts/build-and-test-production.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +# Production Build and Test Script for API Explorer II +# This script builds both frontend and backend, then tests as if on a production server + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}API Explorer II - Production Build Test${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Function to print colored output +print_status() { + echo -e "${GREEN}[✓]${NC} $1" +} + +print_error() { + echo -e "${RED}[✗]${NC} $1" +} + +print_info() { + echo -e "${YELLOW}[i]${NC} $1" +} + +# Check if we're in the right directory +if [ ! -f "package.json" ]; then + print_error "package.json not found. Please run this script from the API-Explorer-II directory." + exit 1 +fi + +print_info "Starting production build and test process..." +echo "" + +# Step 1: Clean previous builds +echo -e "${BLUE}Step 1: Cleaning previous builds${NC}" +if [ -d "dist" ]; then + rm -rf dist + print_status "Removed old frontend build (dist/)" +fi + +if [ -d "dist-server" ]; then + rm -rf dist-server + print_status "Removed old backend build (dist-server/)" +fi +echo "" + +# Step 2: Check environment variables +echo -e "${BLUE}Step 2: Checking environment configuration${NC}" +if [ -f ".env" ]; then + print_status "Found .env file" +else + print_error ".env file not found" + print_info "Copy .env.example to .env and configure it" + exit 1 +fi + +# Check critical environment variables +if grep -q "VITE_OBP_API_HOST" .env; then + print_status "VITE_OBP_API_HOST is configured" +else + print_error "VITE_OBP_API_HOST not found in .env" + exit 1 +fi +echo "" + +# Step 3: Install dependencies +echo -e "${BLUE}Step 3: Installing dependencies${NC}" +print_info "Running npm ci (clean install)..." +npm ci --quiet +print_status "Dependencies installed" +echo "" + +# Step 4: Build frontend +echo -e "${BLUE}Step 4: Building frontend (Vite)${NC}" +print_info "Running: npm run build-only" +npm run build-only +if [ -d "dist" ]; then + DIST_SIZE=$(du -sh dist | cut -f1) + print_status "Frontend built successfully (size: $DIST_SIZE)" +else + print_error "Frontend build failed - dist/ directory not created" + exit 1 +fi +echo "" + +# Step 5: Build backend +echo -e "${BLUE}Step 5: Building backend (TypeScript ES Modules)${NC}" +print_info "Running: npm run build-server" +npm run build-server +if [ -d "dist-server" ]; then + print_status "Backend built successfully" +else + print_error "Backend build failed - dist-server/ directory not created" + exit 1 +fi + +# Check if main app file exists +if [ -f "dist-server/server/app.js" ]; then + print_status "Server entry point found: dist-server/server/app.js" +else + print_error "Server entry point not found: dist-server/server/app.js" + exit 1 +fi +echo "" + +# Step 6: Verify ES Module format +echo -e "${BLUE}Step 6: Verifying ES Module format${NC}" +if head -50 dist-server/server/app.js | grep -E "^import " | head -1 > /dev/null; then + print_status "Backend is using ES modules (import statements found)" +else + print_error "Backend is not using ES modules - CommonJS detected" + exit 1 +fi + +if grep -q '"type": "module"' package.json; then + print_status "package.json has type: module" +else + print_error 'package.json missing "type": "module"' + exit 1 +fi +echo "" + +# Step 7: Check for Redis +echo -e "${BLUE}Step 7: Checking dependencies${NC}" +print_info "Checking if Redis is running..." +if redis-cli ping > /dev/null 2>&1; then + print_status "Redis is running" +else + print_error "Redis is not running" + print_info "Start Redis with: redis-server" + print_info "Or using Docker: docker run -d -p 6379:6379 redis" +fi +echo "" + +# Step 8: Test server startup +echo -e "${BLUE}Step 8: Testing server startup${NC}" +print_info "Starting server for 5 seconds to test..." + +# Start server in background +timeout 5 node dist-server/server/app.js > /tmp/api-explorer-test.log 2>&1 || true + +# Check the log +if grep -q "Backend is running" /tmp/api-explorer-test.log; then + print_status "Server started successfully" + + # Show key startup info + if grep -q "OAuth2Service: Initialization successful" /tmp/api-explorer-test.log; then + print_status "OAuth2 service initialized" + fi + + if grep -q "Connected to Redis" /tmp/api-explorer-test.log; then + print_status "Redis connection established" + fi +else + print_error "Server failed to start" + print_info "Check logs at /tmp/api-explorer-test.log" + cat /tmp/api-explorer-test.log + exit 1 +fi +echo "" + +# Step 9: Show build summary +echo -e "${BLUE}========================================${NC}" +echo -e "${GREEN}Build Summary${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" +echo "Frontend build:" +echo " Location: $(pwd)/dist" +echo " Size: $DIST_SIZE" +echo " Files: $(find dist -type f | wc -l)" +echo "" +echo "Backend build:" +echo " Location: $(pwd)/dist-server" +echo " Entry: dist-server/server/app.js" +echo " Module type: ES Modules" +echo "" +echo "To run in production:" +echo -e " ${YELLOW}node dist-server/server/app.js${NC}" +echo "" +echo "Frontend files can be served by the backend or a web server:" +echo -e " ${YELLOW}The backend serves frontend from dist/ automatically${NC}" +echo "" + +# Step 10: Production readiness checklist +echo -e "${BLUE}========================================${NC}" +echo -e "${YELLOW}Production Readiness Checklist${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" +echo "✓ Frontend built and optimized" +echo "✓ Backend compiled to ES modules" +echo "✓ Server starts without errors" +echo "✓ Dependencies installed" +echo "" +echo "Before deploying to production:" +echo " 1. Configure production .env file" +echo " 2. Ensure Redis is running and accessible" +echo " 3. Set NODE_ENV=production" +echo " 4. Configure OBP API host and credentials" +echo " 5. Set up OAuth2/OIDC client credentials" +echo " 6. Configure process manager (PM2, systemd, etc.)" +echo " 7. Set up reverse proxy (nginx, Apache, etc.)" +echo "" + +print_status "Production build test completed successfully!" +echo "" + +# Cleanup +rm -f /tmp/api-explorer-test.log diff --git a/server/app.ts b/server/app.ts index e94fcfa..b051472 100644 --- a/server/app.ts +++ b/server/app.ts @@ -30,15 +30,31 @@ import 'dotenv/config' import session from 'express-session' import RedisStore from 'connect-redis' import { createClient } from 'redis' -import express, { Application } from 'express' +import express from 'express' +import type { Application } from 'express' import { useExpressServer, useContainer } from 'routing-controllers' import { Container } from 'typedi' import path from 'path' import { execSync } from 'child_process' -import { OAuth2Service } from './services/OAuth2Service' +import { OAuth2Service } from './services/OAuth2Service.js' +import { fileURLToPath } from 'url' +import { dirname } from 'path' -// __dirname is available in CommonJS -// const __dirname is automatically available +// Import controllers +import { OpeyController } from './controllers/OpeyIIController.js' +import { OBPController } from './controllers/RequestController.js' +import { StatusController } from './controllers/StatusController.js' +import { UserController } from './controllers/UserController.js' +import { OAuth2CallbackController } from './controllers/OAuth2CallbackController.js' +import { OAuth2ConnectController } from './controllers/OAuth2ConnectController.js' + +// Import middlewares +import OAuth2AuthorizationMiddleware from './middlewares/OAuth2AuthorizationMiddleware.js' +import OAuth2CallbackMiddleware from './middlewares/OAuth2CallbackMiddleware.js' + +// ES module equivalent of __dirname +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) const port = 8085 const app: Application = express() @@ -151,8 +167,15 @@ let instance: any const server = useExpressServer(app, { routePrefix: routePrefix, - controllers: [path.join(__dirname + '/controllers/*.*s')], - middlewares: [path.join(__dirname + '/middlewares/*.*s')] + controllers: [ + OpeyController, + OBPController, + StatusController, + UserController, + OAuth2CallbackController, + OAuth2ConnectController + ], + middlewares: [OAuth2AuthorizationMiddleware, OAuth2CallbackMiddleware] }) instance = server.listen(port) diff --git a/server/controllers/OAuth2CallbackController.ts b/server/controllers/OAuth2CallbackController.ts index ddb8690..44bc529 100644 --- a/server/controllers/OAuth2CallbackController.ts +++ b/server/controllers/OAuth2CallbackController.ts @@ -26,9 +26,9 @@ */ import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers' -import { Request, Response } from 'express' +import type { Request, Response } from 'express' import { Service } from 'typedi' -import OAuth2CallbackMiddleware from '../middlewares/OAuth2CallbackMiddleware' +import OAuth2CallbackMiddleware from '../middlewares/OAuth2CallbackMiddleware.js' /** * OAuth2 Callback Controller diff --git a/server/controllers/OAuth2ConnectController.ts b/server/controllers/OAuth2ConnectController.ts index 13657b9..dc7f8aa 100644 --- a/server/controllers/OAuth2ConnectController.ts +++ b/server/controllers/OAuth2ConnectController.ts @@ -26,9 +26,9 @@ */ import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers' -import { Request, Response } from 'express' +import type { Request, Response } from 'express' import { Service } from 'typedi' -import OAuth2AuthorizationMiddleware from '../middlewares/OAuth2AuthorizationMiddleware' +import OAuth2AuthorizationMiddleware from '../middlewares/OAuth2AuthorizationMiddleware.js' /** * OAuth2 Connect Controller diff --git a/server/controllers/OpeyIIController.ts b/server/controllers/OpeyIIController.ts index 79418e6..788d065 100644 --- a/server/controllers/OpeyIIController.ts +++ b/server/controllers/OpeyIIController.ts @@ -1,13 +1,13 @@ import { Controller, Session, Req, Res, Post, Get } from 'routing-controllers' -import { Request, Response } from 'express' +import type { Request, Response } from 'express' import { Readable } from 'node:stream' import { ReadableStream as WebReadableStream } from 'stream/web' import { Service, Container } from 'typedi' -import OBPClientService from '../services/OBPClientService' -import OpeyClientService from '../services/OpeyClientService' -import OBPConsentsService from '../services/OBPConsentsService' +import OBPClientService from '../services/OBPClientService.js' +import OpeyClientService from '../services/OpeyClientService.js' +import OBPConsentsService from '../services/OBPConsentsService.js' -import { UserInput, OpeyConfig } from '../schema/OpeySchema' +import { UserInput, OpeyConfig } from '../schema/OpeySchema.js' import { APIApi, Configuration, diff --git a/server/controllers/RequestController.ts b/server/controllers/RequestController.ts index 7cadd8a..4d98714 100644 --- a/server/controllers/RequestController.ts +++ b/server/controllers/RequestController.ts @@ -26,8 +26,8 @@ */ import { Controller, Session, Req, Res, Get, Delete, Post, Put } from 'routing-controllers' -import { Request, Response } from 'express' -import OBPClientService from '../services/OBPClientService' +import type { Request, Response } from 'express' +import OBPClientService from '../services/OBPClientService.js' import { Service, Container } from 'typedi' @Service() diff --git a/server/controllers/StatusController.ts b/server/controllers/StatusController.ts index 5d98a5c..1cde4b4 100644 --- a/server/controllers/StatusController.ts +++ b/server/controllers/StatusController.ts @@ -26,17 +26,17 @@ */ import { Controller, Session, Req, Res, Get } from 'routing-controllers' -import { Request, Response } from 'express' -import OBPClientService from '../services/OBPClientService' +import type { Request, Response } from 'express' +import OBPClientService from '../services/OBPClientService.js' import { Service, Container } from 'typedi' import { OAuthConfig } from 'obp-typescript' -import { commitId } from '../app' +import { commitId } from '../app.js' import { RESOURCE_DOCS_API_VERSION, MESSAGE_DOCS_API_VERSION, API_VERSIONS_LIST_API_VERSION -} from '../../src/shared-constants' +} from '../../src/shared-constants.js' @Service() @Controller('/status') diff --git a/server/controllers/UserController.ts b/server/controllers/UserController.ts index 17e3dd1..e61c59e 100644 --- a/server/controllers/UserController.ts +++ b/server/controllers/UserController.ts @@ -26,11 +26,11 @@ */ import { Controller, Session, Req, Res, Get } from 'routing-controllers' -import { Request, Response } from 'express' -import OBPClientService from '../services/OBPClientService' +import type { Request, Response } from 'express' +import OBPClientService from '../services/OBPClientService.js' import { Service, Container } from 'typedi' -import { OAuth2Service } from '../services/OAuth2Service' -import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants' +import { OAuth2Service } from '../services/OAuth2Service.js' +import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants.js' @Service() @Controller('/user') diff --git a/server/middlewares/OAuth2AuthorizationMiddleware.ts b/server/middlewares/OAuth2AuthorizationMiddleware.ts index c3edf76..8b52a62 100644 --- a/server/middlewares/OAuth2AuthorizationMiddleware.ts +++ b/server/middlewares/OAuth2AuthorizationMiddleware.ts @@ -26,10 +26,10 @@ */ import { ExpressMiddlewareInterface } from 'routing-controllers' -import { Request, Response } from 'express' +import type { Request, Response } from 'express' import { Service, Container } from 'typedi' -import { OAuth2Service } from '../services/OAuth2Service' -import { PKCEUtils } from '../utils/pkce' +import { OAuth2Service } from '../services/OAuth2Service.js' +import { PKCEUtils } from '../utils/pkce.js' /** * OAuth2 Authorization Middleware diff --git a/server/middlewares/OAuth2CallbackMiddleware.ts b/server/middlewares/OAuth2CallbackMiddleware.ts index 094eb51..88dabf4 100644 --- a/server/middlewares/OAuth2CallbackMiddleware.ts +++ b/server/middlewares/OAuth2CallbackMiddleware.ts @@ -26,10 +26,10 @@ */ import { ExpressMiddlewareInterface } from 'routing-controllers' -import { Request, Response } from 'express' +import type { Request, Response } from 'express' import { Service, Container } from 'typedi' -import { OAuth2Service } from '../services/OAuth2Service' -import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants' +import { OAuth2Service } from '../services/OAuth2Service.js' +import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants.js' import jwt from 'jsonwebtoken' /** diff --git a/server/services/OBPClientService.ts b/server/services/OBPClientService.ts index 659639a..0c2fde6 100644 --- a/server/services/OBPClientService.ts +++ b/server/services/OBPClientService.ts @@ -26,7 +26,7 @@ */ import { Service } from 'typedi' -import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants' +import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants.js' // Custom error class to preserve HTTP status codes class OBPAPIError extends Error { diff --git a/server/services/OBPConsentsService.ts b/server/services/OBPConsentsService.ts index 936321c..ca5b113 100644 --- a/server/services/OBPConsentsService.ts +++ b/server/services/OBPConsentsService.ts @@ -8,11 +8,11 @@ import { InlineResponse2017, ErrorUserNotLoggedIn } from 'obp-api-typescript' -import OBPClientService from './OBPClientService' +import OBPClientService from './OBPClientService.js' import { AxiosResponse } from 'axios' import axios from 'axios' import { Session } from 'express-session' -import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants' +import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants.js' @Service() /** diff --git a/server/services/OpeyClientService.ts b/server/services/OpeyClientService.ts index b533644..4b5af16 100644 --- a/server/services/OpeyClientService.ts +++ b/server/services/OpeyClientService.ts @@ -1,6 +1,6 @@ import { Service } from 'typedi' -import { UserInput, StreamInput, OpeyConfig, ConsentRequestResponse } from '../schema/OpeySchema' -import OBPClientService from './OBPClientService' +import { UserInput, StreamInput, OpeyConfig, ConsentRequestResponse } from '../schema/OpeySchema.js' +import OBPClientService from './OBPClientService.js' @Service() export default class OpeyClientService { diff --git a/server/test/OBPClientService.test.ts b/server/test/OBPClientService.test.ts index 331936d..0bfb2cf 100644 --- a/server/test/OBPClientService.test.ts +++ b/server/test/OBPClientService.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from 'vitest' -import OBPClientService from "../services/OBPClientService"; +import OBPClientService from "../services/OBPClientService.js"; import { before } from 'node:test'; describe('OBPClientService.getOauthHeaders', () => { diff --git a/server/test/OBPConsentsService.test.ts b/server/test/OBPConsentsService.test.ts index c7768d9..87dbd13 100644 --- a/server/test/OBPConsentsService.test.ts +++ b/server/test/OBPConsentsService.test.ts @@ -24,8 +24,8 @@ vi.mock('../services/OBPClientService', () => { } }) -import OBPConsentsService from '../services/OBPConsentsService'; -import OpeyClientService from '../services/OpeyClientService'; +import OBPConsentsService from '../services/OBPConsentsService.js'; +import OpeyClientService from '../services/OpeyClientService.js'; describe('OBPConsentsService.createUserConsentsClient', () => { let obpConsentsService: OBPConsentsService; diff --git a/server/test/OpeyClientService.test.ts b/server/test/OpeyClientService.test.ts index bffe40b..cc75e9d 100644 --- a/server/test/OpeyClientService.test.ts +++ b/server/test/OpeyClientService.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest' -import OpeyClientService from '../services/OpeyClientService'; -import { OpeyConfig, UserInput } from '../schema/OpeySchema'; +import OpeyClientService from '../services/OpeyClientService.js'; +import { OpeyConfig, UserInput } from '../schema/OpeySchema.js'; describe('getStatus', async () => { let opeyClientService: OpeyClientService; diff --git a/server/test/opey-controller.test.ts b/server/test/opey-controller.test.ts index e4c08f2..f3b316c 100644 --- a/server/test/opey-controller.test.ts +++ b/server/test/opey-controller.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest' -import { OpeyController } from "../controllers/OpeyIIController"; -import OpeyClientService from '../services/OpeyClientService'; -import OBPClientService from '../services/OBPClientService'; -import OBPConsentsService from '../services/OBPConsentsService'; +import { OpeyController } from "../controllers/OpeyIIController.js"; +import OpeyClientService from '../services/OpeyClientService.js'; +import OBPClientService from '../services/OBPClientService.js'; +import OBPConsentsService from '../services/OBPConsentsService.js'; import Stream, { Readable } from 'stream'; import { Request, Response } from 'express'; import httpMocks from 'node-mocks-http' diff --git a/server/test/opey.test.ts b/server/test/opey.test.ts index 3a3e434..156dc93 100644 --- a/server/test/opey.test.ts +++ b/server/test/opey.test.ts @@ -1,7 +1,7 @@ -import app, { instance } from '../app'; +import app, { instance } from '../app.js'; import request from 'supertest'; import http from 'node:http'; -import { UserInput } from '../schema/OpeySchema'; +import { UserInput } from '../schema/OpeySchema.js'; import {v4 as uuidv4} from 'uuid'; import { agent } from "superagent"; import fetch from 'node-fetch'; diff --git a/tsconfig.server.json b/tsconfig.server.json index 15a1ee3..6b4c256 100644 --- a/tsconfig.server.json +++ b/tsconfig.server.json @@ -1,15 +1,16 @@ { "compilerOptions": { - "module": "CommonJS", - "moduleResolution": "node", + "module": "nodenext", + "moduleResolution": "nodenext", "esModuleInterop": true, - "target": "ES2020", + "target": "ES2022", "outDir": "dist-server", "rootDir": ".", "resolveJsonModule": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowJs": true + "allowJs": true, + "skipLibCheck": true }, "exclude": ["server/test", "node_modules"], "include": ["server", "src/shared-constants.ts"]