feat(cnode): add standalone CLI for Docker server management
- Reorganized server management code: - Moved server_cli.py -> deploy/docker/cnode_cli.py - Moved server_manager.py -> deploy/docker/server_manager.py - Created fast Python-based installation (0.1s startup): - deploy/installer/cnode_pkg/ - Standalone package - deploy/installer/install-cnode.sh - Local installer - deploy/installer/deploy.sh - Remote installer for users - Added backward compatibility: - crawl4ai/cli.py: 'crwl server' redirects to 'cnode' - Updated tests to match new CLI structure (12/12 passing) - Automated sync workflow: - .githooks/pre-commit - Auto-syncs source to package - setup-hooks.sh - One-time setup for contributors - deploy/installer/sync-cnode.sh - Manual sync script Performance: - Startup time: 0.1s (49x faster than PyInstaller) - Size: ~50KB wrapper vs 8.8MB binary Commands: cnode start [--replicas N] # Start server/cluster cnode status # Check status cnode scale N # Scale replicas cnode logs [-f] # View logs cnode stop # Stop server
This commit is contained in:
@@ -65,14 +65,14 @@ class TestCLIBasics:
|
||||
assert 'Crawl4AI CLI' in result.output
|
||||
|
||||
def test_examples(self, runner):
|
||||
result = runner.invoke(cli, ['--example'])
|
||||
result = runner.invoke(cli, ['examples'])
|
||||
assert result.exit_code == 0
|
||||
assert 'Examples' in result.output
|
||||
|
||||
def test_missing_url(self, runner):
|
||||
result = runner.invoke(cli)
|
||||
result = runner.invoke(cli, ['crawl'])
|
||||
assert result.exit_code != 0
|
||||
assert 'URL argument is required' in result.output
|
||||
assert ('Missing argument' in result.output or 'required' in result.output.lower())
|
||||
|
||||
class TestConfigParsing:
|
||||
def test_parse_key_values_basic(self):
|
||||
@@ -101,18 +101,19 @@ class TestConfigLoading:
|
||||
class TestLLMConfig:
|
||||
def test_llm_config_creation(self, temp_config_dir, runner):
|
||||
def input_simulation(inputs):
|
||||
return runner.invoke(cli, ['https://example.com', '-q', 'test question'],
|
||||
return runner.invoke(cli, ['crawl', 'https://example.com', '-q', 'test question'],
|
||||
input='\n'.join(inputs))
|
||||
|
||||
class TestCrawlingFeatures:
|
||||
def test_basic_crawl(self, runner):
|
||||
result = runner.invoke(cli, ['https://example.com'])
|
||||
result = runner.invoke(cli, ['crawl', 'https://example.com'])
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
class TestErrorHandling:
|
||||
def test_invalid_config_file(self, runner):
|
||||
result = runner.invoke(cli, [
|
||||
'crawl',
|
||||
'https://example.com',
|
||||
'--browser-config', 'nonexistent.yml'
|
||||
])
|
||||
@@ -122,8 +123,9 @@ class TestErrorHandling:
|
||||
invalid_schema = temp_config_dir / 'invalid_schema.json'
|
||||
with open(invalid_schema, 'w') as f:
|
||||
f.write('invalid json')
|
||||
|
||||
|
||||
result = runner.invoke(cli, [
|
||||
'crawl',
|
||||
'https://example.com',
|
||||
'--schema', str(invalid_schema)
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user