This commit is contained in:
Executable
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const chokidar = require('chokidar');
|
||||
const path = require('path');
|
||||
|
||||
// Colors for output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m'
|
||||
};
|
||||
|
||||
// App configurations - now only need paths, colors, and nx project names
|
||||
const apps = {
|
||||
shade: { path: 'apps/shade', color: colors.white, nxName: '@tryghost/shade' },
|
||||
design: { path: 'apps/admin-x-design-system', color: colors.cyan, nxName: '@tryghost/admin-x-design-system' },
|
||||
framework: { path: 'apps/admin-x-framework', color: colors.magenta, nxName: '@tryghost/admin-x-framework' },
|
||||
activitypub: { path: 'apps/admin-x-activitypub', color: colors.red, nxName: '@tryghost/admin-x-activitypub' },
|
||||
settings: { path: 'apps/admin-x-settings', color: colors.green, nxName: '@tryghost/admin-x-settings' },
|
||||
posts: { path: 'apps/posts', color: colors.yellow, nxName: '@tryghost/posts' },
|
||||
stats: { path: 'apps/stats', color: colors.blue, nxName: '@tryghost/stats' }
|
||||
};
|
||||
|
||||
// Track all child processes and watchers for cleanup
|
||||
const activeProcesses = new Set();
|
||||
const activeWatchers = new Set();
|
||||
|
||||
function log(appName, message) {
|
||||
const app = apps[appName];
|
||||
console.log(`${app.color}[${appName}]${colors.reset} ${message}`);
|
||||
}
|
||||
|
||||
function buildAllProjects(triggerApp) {
|
||||
return new Promise((resolve) => {
|
||||
log(triggerApp, 'Running nx run-many to rebuild all projects...');
|
||||
const allProjects = Object.values(apps).map(app => app.nxName).join(',');
|
||||
|
||||
const child = spawn('pnpm', ['nx', 'run-many', '-t', 'build', `--projects=${allProjects}`], {
|
||||
cwd: '/home/ghost',
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
NX_DAEMON: 'false'
|
||||
}
|
||||
});
|
||||
|
||||
activeProcesses.add(child);
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
data.toString().split('\n').forEach(line => {
|
||||
if (line.trim()) log(triggerApp, `nx: ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
data.toString().split('\n').forEach(line => {
|
||||
if (line.trim()) log(triggerApp, `nx: ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
activeProcesses.delete(child);
|
||||
if (code === 0) {
|
||||
log(triggerApp, 'All builds complete');
|
||||
} else {
|
||||
log(triggerApp, `Some builds failed with code ${code}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
activeProcesses.delete(child);
|
||||
log(triggerApp, `Build error: ${error.message}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startWatching() {
|
||||
const watchPaths = Object.values(apps).map(app => path.join('/home/ghost', app.path, 'src'));
|
||||
|
||||
console.log('Watching all project src folders for changes...');
|
||||
|
||||
const watcher = chokidar.watch(watchPaths, {
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
usePolling: true,
|
||||
interval: 1000
|
||||
});
|
||||
|
||||
// Track the watcher for cleanup
|
||||
activeWatchers.add(watcher);
|
||||
|
||||
let rebuildTimer;
|
||||
|
||||
watcher.on('all', (event, filePath) => {
|
||||
const relativePath = path.relative('/home/ghost', filePath);
|
||||
|
||||
// Find which project changed for better logging
|
||||
const changedProject = Object.keys(apps).find(name =>
|
||||
filePath.includes(apps[name].path)
|
||||
) || 'unknown';
|
||||
|
||||
log(changedProject, `Change detected: ${event} ${relativePath}`);
|
||||
|
||||
// Debounce rebuilds
|
||||
clearTimeout(rebuildTimer);
|
||||
rebuildTimer = setTimeout(async () => {
|
||||
await buildAllProjects(changedProject);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
watcher.on('error', (error) => {
|
||||
console.log(`Watcher error: ${error.message}`);
|
||||
console.log('Exiting process - Docker will restart the service');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
watcher.on('close', () => {
|
||||
console.log('Watcher closed unexpectedly');
|
||||
console.log('Exiting process - Docker will restart the service');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Starting admin apps build and watch system...');
|
||||
|
||||
try {
|
||||
// Phase 1: Build everything with nx handling dependency order and parallelization
|
||||
const allProjects = Object.values(apps).map(app => app.nxName).join(',');
|
||||
|
||||
const child = spawn('pnpm', ['nx', 'run-many', '-t', 'build', `--projects=${allProjects}`], {
|
||||
cwd: '/home/ghost',
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
NX_DAEMON: 'false'
|
||||
}
|
||||
});
|
||||
|
||||
activeProcesses.add(child);
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
data.toString().split('\n').forEach(line => {
|
||||
if (line.trim()) console.log(`[nx] ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
data.toString().split('\n').forEach(line => {
|
||||
if (line.trim()) console.log(`[nx] ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
child.on('close', (code) => {
|
||||
activeProcesses.delete(child);
|
||||
if (code === 0) {
|
||||
console.log('\nAll projects built successfully!');
|
||||
resolve();
|
||||
} else {
|
||||
console.log(`\nSome builds failed, but continuing with watch processes...`);
|
||||
resolve(); // Don't crash the watch system if some builds fail
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
activeProcesses.delete(child);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Phase 2: Start single watcher for all projects
|
||||
// Single watcher for all projects - any change triggers nx run-many (with caching)
|
||||
const watcher = startWatching();
|
||||
|
||||
console.log('\nAll watch processes started. Press Ctrl+C to stop.');
|
||||
|
||||
// Keep the process alive
|
||||
await new Promise(() => { });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to start:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful shutdown handler
|
||||
function cleanup() {
|
||||
console.log('\nShutting down...');
|
||||
|
||||
// Kill all active child processes
|
||||
for (const child of activeProcesses) {
|
||||
try {
|
||||
child.kill('SIGTERM');
|
||||
// Force kill after 1 second if still running
|
||||
setTimeout(() => {
|
||||
if (!child.killed) {
|
||||
child.kill('SIGKILL');
|
||||
}
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
// Process might already be dead
|
||||
}
|
||||
}
|
||||
|
||||
// Close all watchers
|
||||
for (const watcher of activeWatchers) {
|
||||
try {
|
||||
watcher.close();
|
||||
} catch (error) {
|
||||
// Watcher might already be closed
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Cleanup complete.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle various termination signals
|
||||
process.on('SIGINT', cleanup);
|
||||
process.on('SIGTERM', cleanup);
|
||||
process.on('SIGQUIT', cleanup);
|
||||
|
||||
// Handle uncaught exceptions to ensure cleanup
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught exception:', error);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('Unhandled rejection:', reason);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user