ES modules

This commit is contained in:
simonredfern 2025-12-11 20:44:07 +01:00
parent cf5412173b
commit 37c2688fb5
20 changed files with 296 additions and 52 deletions

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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')

View File

@ -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')

View File

@ -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

View File

@ -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'
/**

View File

@ -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 {

View File

@ -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()
/**

View File

@ -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 {

View File

@ -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', () => {

View File

@ -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;

View File

@ -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;

View File

@ -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'

View File

@ -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';

View File

@ -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"]