pub fn scan(shell: &Shell, current_chain: Option<&str>) -> Result<EcosystemPorts> {
        let ecosystem_config = EcosystemConfig::from_file(shell)?;

        // Create a list of directories to scan:
        // - Ecosystem configs directory
        // - Chain configs directories
        // - External node directories
        // - Ecosystem directory (docker-compose files)
        let mut dirs = vec![ecosystem_config.config.clone()];
        for chain in ecosystem_config.list_of_chains() {
            // skip current chain for avoiding wrong reallocation
            if let Some(name) = current_chain {
                if name.eq(&chain) {
                    continue;
                }
            }
            if let Ok(chain_config) = ecosystem_config.load_chain(Some(chain)) {
                dirs.push(chain_config.configs.clone());
                if let Some(external_node_config_path) = &chain_config.external_node_config_path {
                    dirs.push(external_node_config_path.clone());
                }
            }
        }
        dirs.push(shell.current_dir()); // Ecosystem directory

        let mut ecosystem_ports = EcosystemPorts::default();
        for dir in dirs {
            if dir.is_dir() {
                Self::scan_yaml_files(shell, &dir, &mut ecosystem_ports)
                    .context(format!("Failed to scan directory {:?}", dir))?;
            }
        }

        Ok(ecosystem_ports)
    }
Scans the ecosystem directory for YAML files and extracts port information.
Specifically, it looks for keys ending with "port" or "ports" and collects their values.
We could skip searching ports in the current chain if we initialize this chain.
It allows to reuse default ports even if the general file exist with previously allocated ports.
It makes allocation more predictable
zkstack_cli/crates/zkstack/src/utils/ports.rs