mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-19 03:51:31 +00:00
Merge branch 'main' into refactor/split-server-index
This commit is contained in:
@@ -16,11 +16,12 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const __dirname = getModuleDir(import.meta.url);
|
||||
// The CLI is compiled into dist-server/server, but it still needs to read the top-level
|
||||
// package.json and .env file. Resolving the app root once keeps those lookups stable.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
|
||||
// ANSI color codes for terminal output
|
||||
const colors = {
|
||||
@@ -50,13 +51,16 @@ const c = {
|
||||
};
|
||||
|
||||
// Load package.json for version info
|
||||
const packageJsonPath = path.join(__dirname, '../package.json');
|
||||
const packageJsonPath = path.join(APP_ROOT, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
// Match the runtime fallback in load-env.js so "cloudcli status" reports the same default
|
||||
// database location that the backend will actually use when no DATABASE_PATH is configured.
|
||||
const DEFAULT_DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db');
|
||||
|
||||
// Load environment variables from .env file if it exists
|
||||
function loadEnvFile() {
|
||||
try {
|
||||
const envPath = path.join(__dirname, '../.env');
|
||||
const envPath = path.join(APP_ROOT, '.env');
|
||||
const envFile = fs.readFileSync(envPath, 'utf8');
|
||||
envFile.split('\n').forEach(line => {
|
||||
const trimmedLine = line.trim();
|
||||
@@ -75,12 +79,12 @@ function loadEnvFile() {
|
||||
// Get the database path (same logic as db.js)
|
||||
function getDatabasePath() {
|
||||
loadEnvFile();
|
||||
return process.env.DATABASE_PATH || path.join(__dirname, 'database', 'auth.db');
|
||||
return process.env.DATABASE_PATH || DEFAULT_DATABASE_PATH;
|
||||
}
|
||||
|
||||
// Get the installation directory
|
||||
function getInstallDir() {
|
||||
return path.join(__dirname, '..');
|
||||
return APP_ROOT;
|
||||
}
|
||||
|
||||
// Show status command
|
||||
@@ -124,7 +128,7 @@ function showStatus() {
|
||||
console.log(` Status: ${projectsExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found')}`);
|
||||
|
||||
// Config file location
|
||||
const envFilePath = path.join(__dirname, '../.env');
|
||||
const envFilePath = path.join(APP_ROOT, '.env');
|
||||
const envExists = fs.existsSync(envFilePath);
|
||||
console.log(`\n${c.info('[INFO]')} Configuration File:`);
|
||||
console.log(` ${c.dim(envFilePath)}`);
|
||||
|
||||
@@ -2,11 +2,21 @@ import Database from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import crypto from 'crypto';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js';
|
||||
import {
|
||||
APP_CONFIG_TABLE_SQL,
|
||||
USER_NOTIFICATION_PREFERENCES_TABLE_SQL,
|
||||
VAPID_KEYS_TABLE_SQL,
|
||||
PUSH_SUBSCRIPTIONS_TABLE_SQL,
|
||||
SESSION_NAMES_TABLE_SQL,
|
||||
SESSION_NAMES_LOOKUP_INDEX_SQL,
|
||||
DATABASE_SCHEMA_SQL
|
||||
} from './schema.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const __dirname = getModuleDir(import.meta.url);
|
||||
// The compiled backend lives under dist-server/server/database, but the install root we log
|
||||
// should still point at the project/app root. Resolving it here avoids build-layout drift.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
|
||||
// ANSI color codes for terminal output
|
||||
const colors = {
|
||||
@@ -24,7 +34,6 @@ const c = {
|
||||
|
||||
// Use DATABASE_PATH environment variable if set, otherwise use default location
|
||||
const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db');
|
||||
const INIT_SQL_PATH = path.join(__dirname, 'init.sql');
|
||||
|
||||
// Ensure database directory exists if custom path is provided
|
||||
if (process.env.DATABASE_PATH) {
|
||||
@@ -62,14 +71,10 @@ const db = new Database(DB_PATH);
|
||||
// app_config must exist before any other module imports (auth.js reads the JWT secret at load time).
|
||||
// runMigrations() also creates this table, but it runs too late for existing installations
|
||||
// where auth.js is imported before initializeDatabase() is called.
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS app_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
db.exec(APP_CONFIG_TABLE_SQL);
|
||||
|
||||
// Show app installation path prominently
|
||||
const appInstallPath = path.join(__dirname, '../..');
|
||||
const appInstallPath = APP_ROOT;
|
||||
console.log('');
|
||||
console.log(c.dim('═'.repeat(60)));
|
||||
console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`);
|
||||
@@ -100,53 +105,12 @@ const runMigrations = () => {
|
||||
db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0');
|
||||
}
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS user_notification_preferences (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
preferences_json TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS vapid_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
public_key TEXT NOT NULL,
|
||||
private_key TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
endpoint TEXT NOT NULL UNIQUE,
|
||||
keys_p256dh TEXT NOT NULL,
|
||||
keys_auth TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
// Create app_config table if it doesn't exist (for existing installations)
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS app_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// Create session_names table if it doesn't exist (for existing installations)
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS session_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'claude',
|
||||
custom_name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(session_id, provider)
|
||||
)`);
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider)');
|
||||
db.exec(USER_NOTIFICATION_PREFERENCES_TABLE_SQL);
|
||||
db.exec(VAPID_KEYS_TABLE_SQL);
|
||||
db.exec(PUSH_SUBSCRIPTIONS_TABLE_SQL);
|
||||
db.exec(APP_CONFIG_TABLE_SQL);
|
||||
db.exec(SESSION_NAMES_TABLE_SQL);
|
||||
db.exec(SESSION_NAMES_LOOKUP_INDEX_SQL);
|
||||
|
||||
console.log('Database migrations completed successfully');
|
||||
} catch (error) {
|
||||
@@ -158,8 +122,7 @@ const runMigrations = () => {
|
||||
// Initialize database with schema
|
||||
const initializeDatabase = async () => {
|
||||
try {
|
||||
const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8');
|
||||
db.exec(initSQL);
|
||||
db.exec(DATABASE_SCHEMA_SQL);
|
||||
console.log('Database initialized successfully');
|
||||
runMigrations();
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
-- Initialize authentication database
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- Users table (single user system)
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login DATETIME,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
git_name TEXT,
|
||||
git_email TEXT,
|
||||
has_completed_onboarding BOOLEAN DEFAULT 0
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
||||
|
||||
-- API Keys table for external API access
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
key_name TEXT NOT NULL,
|
||||
api_key TEXT UNIQUE NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_used DATETIME,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active);
|
||||
|
||||
-- User credentials table for storing various tokens/credentials (GitHub, GitLab, etc.)
|
||||
CREATE TABLE IF NOT EXISTS user_credentials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
credential_name TEXT NOT NULL,
|
||||
credential_type TEXT NOT NULL, -- 'github_token', 'gitlab_token', 'bitbucket_token', etc.
|
||||
credential_value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
||||
|
||||
-- User notification preferences (backend-owned, provider-agnostic)
|
||||
CREATE TABLE IF NOT EXISTS user_notification_preferences (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
preferences_json TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- VAPID key pair for Web Push notifications
|
||||
CREATE TABLE IF NOT EXISTS vapid_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
public_key TEXT NOT NULL,
|
||||
private_key TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Browser push subscriptions
|
||||
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
endpoint TEXT NOT NULL UNIQUE,
|
||||
keys_p256dh TEXT NOT NULL,
|
||||
keys_auth TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Session custom names (provider-agnostic display name overrides)
|
||||
CREATE TABLE IF NOT EXISTS session_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'claude',
|
||||
custom_name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(session_id, provider)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);
|
||||
|
||||
-- App configuration table (auto-generated secrets, settings, etc.)
|
||||
CREATE TABLE IF NOT EXISTS app_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
102
server/database/schema.js
Normal file
102
server/database/schema.js
Normal file
@@ -0,0 +1,102 @@
|
||||
export const APP_CONFIG_TABLE_SQL = `CREATE TABLE IF NOT EXISTS app_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`;
|
||||
|
||||
export const USER_NOTIFICATION_PREFERENCES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS user_notification_preferences (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
preferences_json TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);`;
|
||||
|
||||
export const VAPID_KEYS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS vapid_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
public_key TEXT NOT NULL,
|
||||
private_key TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`;
|
||||
|
||||
export const PUSH_SUBSCRIPTIONS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
endpoint TEXT NOT NULL UNIQUE,
|
||||
keys_p256dh TEXT NOT NULL,
|
||||
keys_auth TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);`;
|
||||
|
||||
export const SESSION_NAMES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS session_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'claude',
|
||||
custom_name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(session_id, provider)
|
||||
);`;
|
||||
|
||||
export const SESSION_NAMES_LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);`;
|
||||
|
||||
export const DATABASE_SCHEMA_SQL = `PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login DATETIME,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
git_name TEXT,
|
||||
git_email TEXT,
|
||||
has_completed_onboarding BOOLEAN DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
key_name TEXT NOT NULL,
|
||||
api_key TEXT UNIQUE NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_used DATETIME,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_credentials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
credential_name TEXT NOT NULL,
|
||||
credential_type TEXT NOT NULL,
|
||||
credential_value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
||||
|
||||
${USER_NOTIFICATION_PREFERENCES_TABLE_SQL}
|
||||
|
||||
${VAPID_KEYS_TABLE_SQL}
|
||||
|
||||
${PUSH_SUBSCRIPTIONS_TABLE_SQL}
|
||||
|
||||
${SESSION_NAMES_TABLE_SQL}
|
||||
|
||||
${SESSION_NAMES_LOOKUP_INDEX_SQL}
|
||||
|
||||
${APP_CONFIG_TABLE_SQL}
|
||||
`;
|
||||
@@ -3,13 +3,13 @@
|
||||
import './load-env.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const installMode = fs.existsSync(path.join(__dirname, '..', '.git')) ? 'git' : 'npm';
|
||||
const __dirname = getModuleDir(import.meta.url);
|
||||
// The server source runs from /server, while the compiled output runs from /dist-server/server.
|
||||
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
||||
|
||||
import { c } from './utils/colors.js';
|
||||
|
||||
@@ -326,11 +326,11 @@ app.use('/api/sessions', authenticateToken, messagesRoutes);
|
||||
app.use('/api/agent', agentRoutes);
|
||||
|
||||
// Serve public files (like api-docs.html)
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.use(express.static(path.join(APP_ROOT, 'public')));
|
||||
|
||||
// Static files served after API routes
|
||||
// Add cache control: HTML files should not be cached, but assets can be cached
|
||||
app.use(express.static(path.join(__dirname, '../dist'), {
|
||||
app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
||||
setHeaders: (res, filePath) => {
|
||||
if (filePath.endsWith('.html')) {
|
||||
// Prevent HTML caching to avoid service worker issues after builds
|
||||
@@ -352,7 +352,7 @@ app.use(express.static(path.join(__dirname, '../dist'), {
|
||||
app.post('/api/system/update', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
// Get the project root directory (parent of server directory)
|
||||
const projectRoot = path.join(__dirname, '..');
|
||||
const projectRoot = APP_ROOT;
|
||||
|
||||
console.log('Starting system update from directory:', projectRoot);
|
||||
|
||||
@@ -2194,7 +2194,7 @@ app.get('*', (req, res) => {
|
||||
|
||||
// Only serve index.html for HTML routes, not for static assets
|
||||
// Static assets should already be handled by express.static middleware above
|
||||
const indexPath = path.join(__dirname, '../dist/index.html');
|
||||
const indexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
||||
|
||||
// Check if dist/index.html exists (production build available)
|
||||
if (fs.existsSync(indexPath)) {
|
||||
@@ -2309,7 +2309,7 @@ async function startServer() {
|
||||
configureWebPush();
|
||||
|
||||
// Check if running in production mode (dist folder exists)
|
||||
const distIndexPath = path.join(__dirname, '../dist/index.html');
|
||||
const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
||||
const isProduction = fs.existsSync(distIndexPath);
|
||||
|
||||
// Log Claude implementation mode
|
||||
@@ -2323,7 +2323,7 @@ async function startServer() {
|
||||
console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
|
||||
|
||||
server.listen(SERVER_PORT, HOST, async () => {
|
||||
const appInstallPath = path.join(__dirname, '..');
|
||||
const appInstallPath = APP_ROOT;
|
||||
|
||||
console.log('');
|
||||
console.log(c.dim('═'.repeat(63)));
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const __dirname = getModuleDir(import.meta.url);
|
||||
// Resolve the repo/app root via the nearest /server folder so this file keeps finding the
|
||||
// same top-level .env file from both /server/load-env.js and /dist-server/server/load-env.js.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
|
||||
try {
|
||||
const envPath = path.join(__dirname, '../.env');
|
||||
const envPath = path.join(APP_ROOT, '.env');
|
||||
const envFile = fs.readFileSync(envPath, 'utf8');
|
||||
envFile.split('\n').forEach(line => {
|
||||
const trimmedLine = line.trim();
|
||||
@@ -24,6 +25,10 @@ try {
|
||||
console.log('No .env file found or error reading it:', e.message);
|
||||
}
|
||||
|
||||
// Keep the default database in a stable user-level location so rebuilding dist-server
|
||||
// never changes where the backend stores auth.db when DATABASE_PATH is not set explicitly.
|
||||
const DEFAULT_DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db');
|
||||
|
||||
if (!process.env.DATABASE_PATH) {
|
||||
process.env.DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db');
|
||||
process.env.DATABASE_PATH = DEFAULT_DATABASE_PATH;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import express from 'express';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import os from 'os';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||
import { parseFrontmatter } from '../utils/frontmatter.js';
|
||||
import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const __dirname = getModuleDir(import.meta.url);
|
||||
// This route reads the top-level package.json for the status command, so it needs the real
|
||||
// app root even after compilation moves the route file under dist-server/server/routes.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -291,7 +293,7 @@ Custom commands can be created in:
|
||||
|
||||
'/status': async (args, context) => {
|
||||
// Read version from package.json
|
||||
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
||||
const packageJsonPath = path.join(APP_ROOT, 'package.json');
|
||||
let version = 'unknown';
|
||||
let packageName = 'claude-code-ui';
|
||||
|
||||
|
||||
33
server/tsconfig.json
Normal file
33
server/tsconfig.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
// In the backend config, "@" maps to the /server directory itself.
|
||||
"@/*": ["*"]
|
||||
},
|
||||
// The backend is still mostly JavaScript today, so allowJs lets us add a real
|
||||
// TypeScript build without forcing a large rename before the tooling is usable.
|
||||
"allowJs": true,
|
||||
// Keep the migration incremental: existing JS keeps building, while any new TS files
|
||||
// still go through the normal TypeScript pipeline and strict checks.
|
||||
"checkJs": false,
|
||||
"strict": true,
|
||||
"noEmitOnError": true,
|
||||
// The backend build emits both /server and /shared into dist-server, so rootDir must
|
||||
// stay one level above this file even though the config itself now lives in /server.
|
||||
"rootDir": "..",
|
||||
"outDir": "../dist-server",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.js", "./**/*.ts", "../shared/**/*.js", "../shared/**/*.ts"],
|
||||
"exclude": ["../dist", "../dist-server", "../node_modules", "../src"]
|
||||
}
|
||||
37
server/utils/runtime-paths.js
Normal file
37
server/utils/runtime-paths.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
export function getModuleDir(importMetaUrl) {
|
||||
return path.dirname(fileURLToPath(importMetaUrl));
|
||||
}
|
||||
|
||||
export function findServerRoot(startDir) {
|
||||
// Source files live under /server, while compiled files live under /dist-server/server.
|
||||
// Walking up to the nearest "server" folder gives every backend module one stable anchor
|
||||
// that works in both layouts instead of relying on fragile "../.." assumptions.
|
||||
let currentDir = startDir;
|
||||
|
||||
while (path.basename(currentDir) !== 'server') {
|
||||
const parentDir = path.dirname(currentDir);
|
||||
|
||||
if (parentDir === currentDir) {
|
||||
throw new Error(`Could not resolve the backend server root from "${startDir}".`);
|
||||
}
|
||||
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
return currentDir;
|
||||
}
|
||||
|
||||
export function findAppRoot(startDir) {
|
||||
const serverRoot = findServerRoot(startDir);
|
||||
const parentOfServerRoot = path.dirname(serverRoot);
|
||||
|
||||
// Source files live at <app>/server, while compiled files live at <app>/dist-server/server.
|
||||
// When the nearest server folder sits inside dist-server we need to hop one extra level up
|
||||
// so repo-level files still resolve from the real app root instead of the build directory.
|
||||
return path.basename(parentOfServerRoot) === 'dist-server'
|
||||
? path.dirname(parentOfServerRoot)
|
||||
: parentOfServerRoot;
|
||||
}
|
||||
Reference in New Issue
Block a user