/** * This file handles resolving paths for the server resources * There are two main directories * - 1. the installation directory, exposed by __dirname * - 2. the public directory, exposed by getAppDataPath() */ import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { config } from './config.js'; import { ensureDirectory } from '../utils/fileManagement.js'; import { isProduction } from './environment.js'; /** * Returns public path depending on OS * This is the correct path for the app running in production mode */ export function getAppDataPath(): string { /** * If we are running in docker, the ONTIME_DATA environment variable * allows moving the public directory to a user defined location */ if (process.env.ONTIME_DATA) { return join(process.env.ONTIME_DATA); } switch (process.platform) { case 'darwin': { return join(process.env.HOME!, 'Library', 'Application Support', 'Ontime'); } case 'win32': { return join(process.env.APPDATA!, 'Ontime'); } case 'linux': { return join(process.env.HOME!, '.Ontime'); } default: { throw new Error('Could not resolve public folder for platform'); } } } /** * 1. Paths relative to the installation (or source in development) * ------------------------------------------------------------------ * src/ * ├─ projects/ * ├─ external/ * │ ├─ demo/ * ├─ user/ * │ ├─ styles/ * │ │ ├─ override.css * │ ├─ logo/ * │ │ ├─ logo.png */ /** resolve file URL in both CJS and ESM (build and dev) */ if (import.meta.url) { globalThis.__dirname = fileURLToPath(import.meta.url); } const currentDir = dirname(__dirname); /** * path to server src folder * when running in dev, this file is located in src/setup, so we go one level up * */ const srcDirectory = isProduction ? currentDir : join(currentDir, '../'); export const srcDir = { root: srcDirectory, /** Path to the react app */ clientDir: isProduction ? join(srcDirectory, 'client/') : join(srcDirectory, '../../client/build/'), /** Path to the demo app */ demoDir: join(srcDirectory, 'external', config.demo), } as const; export const srcFiles = { /** Path to start index.html */ clientIndexHtml: join(srcDir.clientDir, 'index.html'), /** Path to bundled CSS */ cssOverride: join(srcDir.root, config.user, config.styles.directory, config.styles.filename), /** Path to bundled external readme */ externalReadme: join(srcDir.root, config.external, 'README.md'), /** Path to bundled user readme */ userReadme: join(srcDir.root, config.user, 'README.md'), /** Path to bundled CSS readme */ cssReadme: join(srcDir.root, config.user, config.styles.directory, 'README.md'), /** Path to login */ login: join(srcDir.root, 'html/login.html'), }; /** * 2. Paths relative to the user public directory * ------------------------------------------------ */ /** Resolve root to public directory */ const resolvePublicDirectory = getAppDataPath(); // Ensure directory tree is created ensureDirectory(resolvePublicDirectory); /** * Path to external * This is unique in the way that we use the src directory in development * For simplicity we still bundle this in the public directory object */ const externalsStartDirectory = isProduction ? resolvePublicDirectory : join(srcDirectory, 'external'); export const publicDir = { root: resolvePublicDirectory, /** path to crash reports folder */ crashDir: join(resolvePublicDirectory, config.crash), /** path to projects folder */ projectsDir: join(resolvePublicDirectory, config.projects), /** path to corrupt folder */ corruptDir: join(resolvePublicDirectory, config.corrupt), /** path to uploads folder */ uploadsDir: join(resolvePublicDirectory, config.uploads), /** path to external folder */ externalDir: join( externalsStartDirectory, isProduction ? config.external : '', // move to external folder in production ), /** path to demo project folder */ demoDir: join( externalsStartDirectory, isProduction ? '/external/' : '', // move to external folder in production config.demo, ), /** path to user folder */ userDir: join(resolvePublicDirectory, config.user), /** path to external styles override */ stylesDir: join(resolvePublicDirectory, config.user, config.styles.directory), logoDir: join(resolvePublicDirectory, config.user, config.logo), } as const; /** * Resolve path to specific files */ export const publicFiles = { /** path to app state file */ appState: join(publicDir.root, config.appState), /** path to restore file */ restoreFile: join(publicDir.root, config.restoreFile), /** path to CSS override file */ cssOverride: join(publicDir.stylesDir, config.styles.filename), /** path to external readme file */ externalReadme: join(publicDir.externalDir, 'README.md'), /** path to user readme file */ userReadme: join(publicDir.userDir, 'README.md'), /** path to CSS readme file */ cssReadme: join(publicDir.stylesDir, 'README.md'), } as const;