Enhance meet.sh and typings scripts: add typings watch scripts for CE and PRO, and improve flag handling in watch-with-typings-guard.mjs

This commit is contained in:
Carlos Santos 2025-10-20 17:55:19 +02:00
parent a33d9d68ec
commit 56343e8f03
5 changed files with 199 additions and 63 deletions

22
meet.sh
View File

@ -120,7 +120,7 @@ show_help() {
echo " Start development mode with watchers" echo " Start development mode with watchers"
echo echo
echo -e " ${BLUE}start${NC}" echo -e " ${BLUE}start${NC}"
echo " Start services in production or CI mode" echo " Start OpenVidu Meet in production or CI mode"
echo -e " ${YELLOW}Options:${NC} --prod Start in production mode" echo -e " ${YELLOW}Options:${NC} --prod Start in production mode"
echo -e " ${NC} --ci Start in CI mode" echo -e " ${NC} --ci Start in CI mode"
echo echo
@ -376,10 +376,10 @@ add_common_dev_commands() {
CMD_COLORS+=("bgRed.white") CMD_COLORS+=("bgRed.white")
CMD_COMMANDS+=("npm --prefix $OV_COMPONENTS_DIR install && npm --prefix $OV_COMPONENTS_DIR run lib:serve") CMD_COMMANDS+=("npm --prefix $OV_COMPONENTS_DIR install && npm --prefix $OV_COMPONENTS_DIR run lib:serve")
# Typings watcher # Typings watcher. It generates the typings-ready.flag file when done for other watchers to wait on.
CMD_NAMES+=("typings-ce") CMD_NAMES+=("typings-ce")
CMD_COLORS+=("bgGreen.black") CMD_COLORS+=("bgGreen.black")
CMD_COMMANDS+=("./scripts/dev/watch-typings.sh") CMD_COMMANDS+=("./scripts/dev/watch-typings-ce.sh")
# shared-meet-components watcher # shared-meet-components watcher
CMD_NAMES+=("shared-meet-components") CMD_NAMES+=("shared-meet-components")
@ -410,12 +410,12 @@ add_pro_commands() {
# Run backend-pro # Run backend-pro
CMD_NAMES+=("backend-pro") CMD_NAMES+=("backend-pro")
CMD_COLORS+=("blue") CMD_COLORS+=("cyan")
CMD_COMMANDS+=("node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run dev:pro-backend'") CMD_COMMANDS+=("node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run dev:pro-backend'")
# Watch backend-ce # Watch backend-ce
CMD_NAMES+=("backend-ce-watch") CMD_NAMES+=("backend-ce-watch")
CMD_COLORS+=("cyan") CMD_COLORS+=("bgCyan.white")
CMD_COMMANDS+=("node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run --filter @openvidu-meet/backend build:watch'") CMD_COMMANDS+=("node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run --filter @openvidu-meet/backend build:watch'")
# Run frontend-pro after components-angular and shared-meet-components are ready # Run frontend-pro after components-angular and shared-meet-components are ready
@ -423,10 +423,10 @@ add_pro_commands() {
CMD_COLORS+=("magenta") CMD_COLORS+=("magenta")
CMD_COMMANDS+=("wait-on ${components_path} && wait-on ${shared_meet_components_path} && sleep 1 && node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run dev:pro-frontend'") CMD_COMMANDS+=("wait-on ${components_path} && wait-on ${shared_meet_components_path} && sleep 1 && node ./scripts/dev/watch-with-typings-guard.mjs 'pnpm run dev:pro-frontend'")
# Run @openvidu-meet-pro/typings watcher # Typings watcher for PRO edition. It generates the typings-ready.flag file when done for other watchers to wait on.
CMD_NAMES+=("typings-pro") CMD_NAMES+=("typings-pro")
CMD_COLORS+=("brightGreen") CMD_COLORS+=("bgGreen.black")
CMD_COMMANDS+=("pnpm --filter @openvidu-meet-pro/typings run build:watch") CMD_COMMANDS+=("./scripts/dev/watch-typings-pro.sh")
} }
# Helper: Add REST API docs and browser-sync commands # Helper: Add REST API docs and browser-sync commands
@ -543,8 +543,8 @@ dev() {
launch_dev_watchers "$edition" "$components_path" launch_dev_watchers "$edition" "$components_path"
} }
# Start services # Start OpenVidu Meet services in prod or ci mode
start_services() { start() {
MODE="" MODE=""
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
@ -820,7 +820,7 @@ main() {
dev dev
;; ;;
start) start)
start_services "$@" start "$@"
;; ;;
start-testapp) start-testapp)
start_testapp start_testapp

View File

@ -6,6 +6,7 @@ packages:
- meet-ce/backend - meet-ce/backend
- meet-pro/frontend - meet-pro/frontend
- meet-pro/backend - meet-pro/backend
- meet-pro/typings
- testapp - testapp
ignoredBuiltDependencies: ignoredBuiltDependencies:

View File

@ -1,4 +1,6 @@
#!/bin/bash #!/bin/bash
# Path to the flag file indicating typings are ready
FLAG_PATH="./meet-ce/typings/dist/typings-ready.flag" FLAG_PATH="./meet-ce/typings/dist/typings-ready.flag"
# Remove the flag file if it exists # Remove the flag file if it exists

View File

@ -0,0 +1,38 @@
#!/bin/bash
# Path to the flag file indicating typings are ready
FLAG_PATH="./meet-pro/typings/dist/typings-ready.flag"
# Remove the flag file if it exists
rm -f $FLAG_PATH
# Create an empty directory for the flag file if it doesn't exist
mkdir -p "$(dirname "$FLAG_PATH")"
echo "Starting typings watch mode..."
echo "Waiting for initial compilation..."
# Run tsc in watch mode
pnpm --filter @openvidu-meet-pro/typings run dev | while read line; do
echo "$line"
# Check for compilation start (remove flag to signal "not ready")
if echo "$line" | grep -q "File change detected. Starting incremental compilation"; then
rm -f $FLAG_PATH
echo "Typings recompiling..."
fi
# Check for successful compilation (create flag to signal "ready")
if echo "$line" | grep -q "Found 0 errors"; then
# Add small delay to ensure all files are written to disk
sleep 0.2
touch $FLAG_PATH
echo "✅ Typings ready!"
fi
# Check for compilation errors
if echo "$line" | grep -q "error TS"; then
rm -f $FLAG_PATH
echo "❌ Typings compilation failed!"
fi
done

View File

@ -12,8 +12,11 @@ const __dirname = dirname(__filename);
// Configuration // Configuration
const CE_TYPINGS_FLAG_PATH = resolve(__dirname, '../../meet-ce/typings/dist/typings-ready.flag'); const CE_TYPINGS_FLAG_PATH = resolve(__dirname, '../../meet-ce/typings/dist/typings-ready.flag');
const CE_TYPINGS_DIST = resolve(__dirname, '../../meet-ce/typings/dist'); const CE_TYPINGS_DIST = resolve(__dirname, '../../meet-ce/typings/dist');
const PRO_TYPINGS_FLAG_PATH = resolve(__dirname, '../../meet-pro/typings/dist/typings-ready.flag');
const PRO_TYPINGS_DIST = resolve(__dirname, '../../meet-pro/typings/dist');
const DEBOUNCE_MS = 500; // Wait 500ms after flag appears before restarting const DEBOUNCE_MS = 500; // Wait 500ms after flag appears before restarting
const KILL_TIMEOUT_MS = 5000; // Max time to wait for process to die const KILL_TIMEOUT_MS = 5000; // Max time to wait for process to die
const POLL_DIR_MS = 1000; // Polling interval for directories that don't yet exist
// Get command from arguments // Get command from arguments
const command = process.argv.slice(2).join(' '); const command = process.argv.slice(2).join(' ');
@ -25,7 +28,8 @@ if (!command) {
} }
let childProcess = null; let childProcess = null;
let isTypingsReady = false; // We'll support multiple targets (CE and PRO). Each target has its own ready state.
const targets = [];
let pendingRestart = null; let pendingRestart = null;
let hasStartedOnce = false; let hasStartedOnce = false;
let isKilling = false; let isKilling = false;
@ -34,9 +38,12 @@ let isKilling = false;
* Start the child process * Start the child process
*/ */
async function startProcess() { async function startProcess() {
if (!isTypingsReady) { // Process should start only when all required targets report ready
const allReady = targets.length > 0 && targets.every((t) => t.isReady);
if (!allReady) {
if (!hasStartedOnce) { if (!hasStartedOnce) {
console.log('Waiting for typings to be ready...'); const names = targets.map((t) => t.name).join(', ');
console.log(`Waiting for typings to be ready for: ${names}...`);
} }
return; return;
} }
@ -155,86 +162,174 @@ function scheduleRestart() {
* Check if typings are ready * Check if typings are ready
*/ */
function checkTypingsReady() { function checkTypingsReady() {
const wasReady = isTypingsReady; let changed = false;
isTypingsReady = existsSync(CE_TYPINGS_FLAG_PATH); for (const t of targets) {
const wasReady = t.isReady;
if (!wasReady && isTypingsReady) { t.isReady = existsSync(t.flagPath);
console.log('✅ Typings are ready!'); if (!wasReady && t.isReady) {
scheduleRestart(); console.log(`✅ Typings ready for ${t.name}!`);
} else if (wasReady && !isTypingsReady) { changed = true;
console.log('Typings recompiling... (process will restart when ready)'); } else if (wasReady && !t.isReady) {
console.log(`Typings recompiling for ${t.name}... (process will restart when ready)`);
changed = true;
}
} }
return isTypingsReady; if (changed) {
scheduleRestart();
}
return targets.every((t) => t.isReady);
} }
/** /**
* Watch the flag file * Watch the flag file
*/ */
function watchFlag() { /**
console.log(`Watching typings flag: ${CE_TYPINGS_FLAG_PATH}`); * Create a watcher for a single target that watches its flag dir and dist dir.
* If the directories don't exist yet, we poll until they appear then create watchers.
*/
function watchTarget(target) {
console.log(`Setting up watchers for ${target.name}`);
// Initial check // Initial check
checkTypingsReady(); checkTypingsReady();
// Watch the parent directory of the flag const flagDir = dirname(target.flagPath);
const flagDir = dirname(CE_TYPINGS_FLAG_PATH); const distDir = target.distPath;
const watcher = watch(flagDir, { recursive: false }, (eventType, filename) => { function createWatchers() {
try {
if (!target.flagWatcher && existsSync(flagDir)) {
target.flagWatcher = watch(flagDir, { recursive: false }, (eventType, filename) => {
if (filename === 'typings-ready.flag') { if (filename === 'typings-ready.flag') {
checkTypingsReady(); checkTypingsReady();
} }
}); });
watcher.on('error', (error) => { target.flagWatcher.on('error', (error) => {
console.error('❌ Watcher error:', error); console.error(`❌ Watcher error for ${target.name} flag:`, error);
});
}
if (!target.distWatcher && existsSync(distDir)) {
target.distWatcher = watch(distDir, { recursive: true }, (eventType, filename) => {
if (filename === 'typings-ready.flag') return;
if (childProcess && target.isReady) {
console.log(`Detected change in ${target.name} typings: ${filename} (will restart when compilation finishes)`);
}
}); });
return watcher; target.distWatcher.on('error', (error) => {
console.error(`❌ Watcher error for ${target.name} dist:`, error);
});
}
// If we have at least one watcher, stop polling
if ((target.flagWatcher || target.distWatcher) && target.poller) {
clearInterval(target.poller);
target.poller = null;
}
} catch (err) {
// Rare race - ignore and keep polling
// console.error(`Error creating watcher for ${target.name}:`, err.message);
}
}
// If directories are already present, create watchers immediately
if (existsSync(flagDir) || existsSync(distDir)) {
createWatchers();
}
// Start polling for directory creation if watchers not yet created
if (!target.flagWatcher && !target.distWatcher) {
target.poller = setInterval(() => {
createWatchers();
}, POLL_DIR_MS);
}
return () => {
if (target.flagWatcher) {
try { target.flagWatcher.close(); } catch (e) {}
target.flagWatcher = null;
}
if (target.distWatcher) {
try { target.distWatcher.close(); } catch (e) {}
target.distWatcher = null;
}
if (target.poller) {
clearInterval(target.poller);
target.poller = null;
}
};
} }
/** /**
* Watch typings/dist for changes (to trigger restart when ready) * Watch typings/dist for changes (to trigger restart when ready)
*/ */
function watchTypingsDist() { // Setup targets depending on the provided command. If command mentions meet-pro we include PRO
console.log(`Watching typings dist: ${CE_TYPINGS_DIST}`); function setupTargetsFromCommand(cmd) {
const normalized = (cmd || '').toLowerCase();
const usePro = /\b(?:meet-pro|pro)\b/.test(normalized);
const watcher = watch(CE_TYPINGS_DIST, { recursive: true }, (eventType, filename) => { // Always include CE by default unless the command explicitly targets only pro and not CE.
// Ignore the flag file itself (handled by watchFlag) // If both are mentioned include both.
if (filename === 'typings-ready.flag') { const includeCE = true; // keep CE by default
return;
}
// Only log changes, but don't restart yet if (includeCE) {
// Restart will happen when flag is recreated (via checkTypingsReady) targets.push({
if (childProcess && isTypingsReady) { name: 'CE',
console.log(`Detected change in typings: ${filename} (will restart when compilation finishes)`); flagPath: CE_TYPINGS_FLAG_PATH,
} distPath: CE_TYPINGS_DIST,
isReady: false,
flagWatcher: null,
distWatcher: null,
poller: null,
}); });
watcher.on('error', (error) => {
console.error('❌ Watcher error:', error);
});
return watcher;
} }
// Start watching if (usePro) {
const flagWatcher = watchFlag(); targets.push({
const distWatcher = watchTypingsDist(); name: 'PRO',
flagPath: PRO_TYPINGS_FLAG_PATH,
distPath: PRO_TYPINGS_DIST,
isReady: false,
flagWatcher: null,
distWatcher: null,
poller: null,
});
}
// If the command only mentions PRO and you don't want CE, you could tweak logic here.
}
// Setup targets based on command
setupTargetsFromCommand(command);
// Create watchers for each target and keep cleanup functions
const cleanupFns = [];
for (const t of targets) {
const cleanup = watchTarget(t);
cleanupFns.push(cleanup);
}
// Kick an initial check/start attempt (in case flags already ready)
checkTypingsReady();
// Cleanup on exit // Cleanup on exit
process.on('SIGINT', async () => { async function doCleanupAndExit(code = 0) {
console.log('\n🛑 Stopping...'); console.log('\n🛑 Stopping...');
await killProcess(); await killProcess();
flagWatcher.close(); for (const fn of cleanupFns) {
distWatcher.close(); try { fn(); } catch (e) {}
process.exit(0); }
process.exit(code);
}
process.on('SIGINT', async () => {
await doCleanupAndExit(0);
}); });
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
await killProcess(); await doCleanupAndExit(0);
flagWatcher.close();
distWatcher.close();
process.exit(0);
}); });