149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import Chokidar from 'chokidar';
|
|
import fs from 'fs';
|
|
import { exec } from '../utils/exec';
|
|
import ChildProcess from 'child_process';
|
|
import kill from 'tree-kill';
|
|
import AutheliaServerInterface from './AutheliaServerInterface';
|
|
|
|
class AutheliaServerWithHotReload implements AutheliaServerInterface {
|
|
private watcher: Chokidar.FSWatcher;
|
|
private configPath: string;
|
|
private AUTHELIA_INTERRUPT_FILENAME = '.authelia-interrupt';
|
|
private serverProcess: ChildProcess.ChildProcess | undefined;
|
|
private clientProcess: ChildProcess.ChildProcess | undefined;
|
|
|
|
constructor(configPath: string) {
|
|
this.configPath = configPath;
|
|
this.watcher = Chokidar.watch(['server', 'shared/**/*.ts', 'node_modules',
|
|
this.AUTHELIA_INTERRUPT_FILENAME, configPath], {
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
});
|
|
}
|
|
|
|
private async startServer() {
|
|
await exec('./node_modules/.bin/tslint -c server/tslint.json -p server/tsconfig.json')
|
|
this.serverProcess = ChildProcess.spawn('./node_modules/.bin/ts-node',
|
|
['-P', './server/tsconfig.json', './server/src/index.ts', this.configPath], {
|
|
env: {...process.env},
|
|
});
|
|
this.serverProcess.stdout.pipe(process.stdout);
|
|
this.serverProcess.stderr.pipe(process.stderr);
|
|
this.serverProcess.on('exit', () => {
|
|
console.log('Authelia server exited.');
|
|
if (!this.serverProcess) return;
|
|
this.serverProcess.removeAllListeners();
|
|
this.serverProcess = undefined;
|
|
});
|
|
}
|
|
|
|
private killServer() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.serverProcess) {
|
|
try {
|
|
const timeout = setTimeout(
|
|
() => reject(new Error('Server not killed after timeout.')), 10000);
|
|
this.serverProcess.on('exit', () => {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
});
|
|
kill(this.serverProcess.pid, 'SIGKILL');
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
private async startClient() {
|
|
this.clientProcess = ChildProcess.spawn('npm', ['run', 'start'], {
|
|
cwd: './client',
|
|
env: {
|
|
...process.env,
|
|
'BROWSER': 'none'
|
|
}
|
|
});
|
|
this.clientProcess.stdout.pipe(process.stdout);
|
|
this.clientProcess.stderr.pipe(process.stderr);
|
|
this.clientProcess.on('exit', () => {
|
|
console.log('Authelia client exited.');
|
|
if (!this.clientProcess) return;
|
|
this.clientProcess.removeAllListeners();
|
|
this.clientProcess = undefined;
|
|
})
|
|
}
|
|
|
|
private killClient() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.clientProcess) {
|
|
try {
|
|
const timeout = setTimeout(
|
|
() => reject(new Error('Server not killed after timeout.')), 10000);
|
|
this.clientProcess.on('exit', () => {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
});
|
|
kill(this.clientProcess.pid, 'SIGKILL');
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
private async generateConfigurationSchema() {
|
|
await exec('./node_modules/.bin/typescript-json-schema -o ' +
|
|
'server/src/lib/configuration/Configuration.schema.json ' +
|
|
'--strictNullChecks --required server/tsconfig.json Configuration');
|
|
}
|
|
|
|
/**
|
|
* Handle file changes.
|
|
* @param path The path of the file that has been changed.
|
|
*/
|
|
private async onFileChanged(path: string) {
|
|
console.log(`File ${path} has been changed, reloading...`);
|
|
if (path.startsWith('server/src/lib/configuration/schema')) {
|
|
console.log('Schema needs to be regenerated.');
|
|
await this.generateConfigurationSchema();
|
|
}
|
|
else if (path === this.AUTHELIA_INTERRUPT_FILENAME) {
|
|
if (fs.existsSync(path)) {
|
|
console.log('Authelia is being interrupted.');
|
|
await this.killServer();
|
|
} else {
|
|
console.log('Authelia is restarting.');
|
|
await this.startServer();
|
|
}
|
|
return;
|
|
}
|
|
await this.killServer();
|
|
await this.startServer();
|
|
}
|
|
|
|
async start() {
|
|
if (fs.existsSync(this.AUTHELIA_INTERRUPT_FILENAME)) {
|
|
console.log('Authelia is interrupted. Consider removing ' + this.AUTHELIA_INTERRUPT_FILENAME + ' if it\'s not expected.');
|
|
return;
|
|
}
|
|
|
|
console.log('Start watching file changes...');
|
|
this.watcher.on('add', (p) => this.onFileChanged(p));
|
|
this.watcher.on('unlink', (p) => this.onFileChanged(p));
|
|
this.watcher.on('change', (p) => this.onFileChanged(p));
|
|
|
|
this.startClient();
|
|
this.startServer();
|
|
}
|
|
|
|
async stop() {
|
|
await this.killClient();
|
|
await this.killServer();
|
|
}
|
|
}
|
|
|
|
export default AutheliaServerWithHotReload; |