• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 5045835811

pending completion
5045835811

Pull #1697

github

GitHub
Merge c3e3c7443 into a16355c73
Pull Request #1697: perf: native_function_invocation

321 of 321 new or added lines in 62 files covered. (100.0%)

2784 of 4121 relevant lines covered (67.56%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Command/Serve.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <arnaud@ligny.fr>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace Cecil\Command;
15

16
use Cecil\Exception\RuntimeException;
17
use Cecil\Util;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputDefinition;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Process\Exception\ProcessFailedException;
26
use Symfony\Component\Process\PhpExecutableFinder;
27
use Symfony\Component\Process\Process;
28
use Yosymfony\ResourceWatcher\Crc32ContentHash;
29
use Yosymfony\ResourceWatcher\ResourceCacheMemory;
30
use Yosymfony\ResourceWatcher\ResourceWatcher;
31

32
/**
33
 * Starts the built-in server.
34
 */
35
class Serve extends AbstractCommand
36
{
37
    /**
38
     * {@inheritdoc}
39
     */
40
    protected function configure()
41
    {
42
        $this
×
43
            ->setName('serve')
×
44
            ->setDescription('Starts the built-in server')
×
45
            ->setDefinition(
×
46
                new InputDefinition([
×
47
                    new InputArgument('path', InputArgument::OPTIONAL, 'Use the given path as working directory'),
×
48
                    new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Set the path to extra config files (comma-separated)'),
×
49
                    new InputOption('drafts', 'd', InputOption::VALUE_NONE, 'Include drafts'),
×
50
                    new InputOption('page', 'p', InputOption::VALUE_REQUIRED, 'Build a specific page'),
×
51
                    new InputOption('open', 'o', InputOption::VALUE_NONE, 'Open web browser automatically'),
×
52
                    new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Server host'),
×
53
                    new InputOption('port', null, InputOption::VALUE_REQUIRED, 'Server port'),
×
54
                    new InputOption('postprocess', null, InputOption::VALUE_OPTIONAL, 'Post-process output (disable with "no")', false),
×
55
                    new InputOption('clear-cache', null, InputOption::VALUE_OPTIONAL, 'Clear cache before build (optional cache key regular expression)', false),
×
56
                ])
×
57
            )
×
58
            ->setHelp('Starts the live-reloading-built-in web server');
×
59
    }
60

61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @throws RuntimeException
65
     */
66
    protected function execute(InputInterface $input, OutputInterface $output)
67
    {
68
        $drafts = $input->getOption('drafts');
×
69
        $open = $input->getOption('open');
×
70
        $host = $input->getOption('host') ?? 'localhost';
×
71
        $port = $input->getOption('port') ?? '8000';
×
72
        $postprocess = $input->getOption('postprocess');
×
73
        $clearcache = $input->getOption('clear-cache');
×
74
        $verbose = $input->getOption('verbose');
×
75
        $page = $input->getOption('page');
×
76

77
        $this->setUpServer($host, $port);
×
78

79
        $phpFinder = new PhpExecutableFinder();
×
80
        $php = $phpFinder->find();
×
81
        if ($php === false) {
×
82
            throw new RuntimeException('Can\'t find a local PHP executable.');
×
83
        }
84

85
        $command = sprintf(
×
86
            '%s -S %s:%d -t %s %s',
×
87
            $php,
×
88
            $host,
×
89
            $port,
×
90
            $this->getPath() . '/' . (string) $this->getBuilder()->getConfig()->get('output.dir'),
×
91
            Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php')
×
92
        );
×
93
        $process = Process::fromShellCommandline($command);
×
94

95
        $buildProcessArguments = [
×
96
            $php,
×
97
            $_SERVER['argv'][0],
×
98
        ];
×
99
        $buildProcessArguments[] = 'build';
×
100
        if (!empty($this->getConfigFiles())) {
×
101
            $buildProcessArguments[] = '--config';
×
102
            $buildProcessArguments[] = implode(',', $this->getConfigFiles());
×
103
        }
104
        if ($drafts) {
×
105
            $buildProcessArguments[] = '--drafts';
×
106
        }
107
        if ($postprocess === null) {
×
108
            $buildProcessArguments[] = '--postprocess';
×
109
        }
110
        if (!empty($postprocess)) {
×
111
            $buildProcessArguments[] = '--postprocess';
×
112
            $buildProcessArguments[] = $postprocess;
×
113
        }
114
        if ($clearcache === null) {
×
115
            $buildProcessArguments[] = '--clear-cache';
×
116
        }
117
        if (!empty($clearcache)) {
×
118
            $buildProcessArguments[] = '--clear-cache';
×
119
            $buildProcessArguments[] = $clearcache;
×
120
        }
121
        if ($verbose) {
×
122
            $buildProcessArguments[] = '-' . str_repeat('v', $_SERVER['SHELL_VERBOSITY']);
×
123
        }
124
        if (!empty($page)) {
×
125
            $buildProcessArguments[] = '--page';
×
126
            $buildProcessArguments[] = $page;
×
127
        }
128

129
        $buildProcess = new Process(array_merge($buildProcessArguments, [$this->getPath()]));
×
130

131
        if ($this->getBuilder()->isDebug()) {
×
132
            $output->writeln(sprintf('<comment>Process: %s</comment>', implode(' ', $buildProcessArguments)));
×
133
        }
134

135
        $buildProcess->setTty(Process::isTtySupported());
×
136
        $buildProcess->setPty(Process::isPtySupported());
×
137
        $buildProcess->setTimeout(3600 * 2); // timeout = 2 minutes
×
138

139
        $processOutputCallback = function ($type, $data) use ($output) {
×
140
            $output->write($data, false, OutputInterface::OUTPUT_RAW);
×
141
        };
×
142

143
        // (re)builds before serve
144
        $buildProcess->run($processOutputCallback);
×
145
        if ($buildProcess->isSuccessful()) {
×
146
            Util\File::getFS()->dumpFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'changes.flag'), time());
×
147
        }
148
        if ($buildProcess->getExitCode() !== 0) {
×
149
            return 1;
×
150
        }
151

152
        // handles process
153
        if (!$process->isStarted()) {
×
154
            // set resource watcher
155
            $finder = new Finder();
×
156
            $finder->files()
×
157
                ->in($this->getPath())
×
158
                ->exclude($this->getBuilder()->getConfig()->getOutputPath());
×
159
            if (file_exists(Util::joinFile($this->getPath(), '.gitignore'))) {
×
160
                $finder->ignoreVCSIgnored(true);
×
161
            }
162
            $hashContent = new Crc32ContentHash();
×
163
            $resourceCache = new ResourceCacheMemory();
×
164
            $resourceWatcher = new ResourceWatcher($resourceCache, $finder, $hashContent);
×
165
            $resourceWatcher->initialize();
×
166

167
            // starts server
168
            try {
169
                if (\function_exists('\pcntl_signal')) {
×
170
                    pcntl_async_signals(true);
×
171
                    pcntl_signal(SIGINT, [$this, 'tearDownServer']);
×
172
                    pcntl_signal(SIGTERM, [$this, 'tearDownServer']);
×
173
                }
174
                $output->writeln(
×
175
                    sprintf('Starting server (<href=http://%s:%d>%s:%d</>)...', $host, $port, $host, $port)
×
176
                );
×
177
                $process->start();
×
178
                if ($open) {
×
179
                    $output->writeln('Opening web browser...');
×
180
                    Util\Plateform::openBrowser(sprintf('http://%s:%s', $host, $port));
×
181
                }
182
                while ($process->isRunning()) {
×
183
                    if ($resourceWatcher->findChanges()->hasChanges()) {
×
184
                        // re-builds
185
                        $output->writeln('<comment>Changes detected.</comment>');
×
186
                        $output->writeln('');
×
187

188
                        $buildProcess->run($processOutputCallback);
×
189
                        if ($buildProcess->isSuccessful()) {
×
190
                            Util\File::getFS()->dumpFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'changes.flag'), time());
×
191
                        }
192

193
                        $output->writeln('<info>Server is runnning...</info>');
×
194
                    }
195
                }
196
            } catch (ProcessFailedException $e) {
×
197
                $this->tearDownServer();
×
198

199
                throw new RuntimeException(sprintf($e->getMessage()));
×
200
            }
201
        }
202

203
        return 0;
×
204
    }
205

206
    /**
207
     * Prepares server's files.
208
     *
209
     * @throws RuntimeException
210
     */
211
    private function setUpServer(string $host, string $port): void
212
    {
213
        try {
214
            $root = Util::joinFile(__DIR__, '../../');
×
215
            if (Util\Plateform::isPhar()) {
×
216
                $root = Util\Plateform::getPharPath() . '/';
×
217
            }
218
            // copying router
219
            Util\File::getFS()->copy(
×
220
                $root . '/resources/server/router.php',
×
221
                Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'),
×
222
                true
×
223
            );
×
224
            // copying livereload JS
225
            Util\File::getFS()->copy(
×
226
                $root . '/resources/server/livereload.js',
×
227
                Util::joinFile($this->getPath(), self::TMP_DIR, 'livereload.js'),
×
228
                true
×
229
            );
×
230
            // copying baseurl text file
231
            Util\File::getFS()->dumpFile(
×
232
                Util::joinFile($this->getPath(), self::TMP_DIR, 'baseurl'),
×
233
                sprintf(
×
234
                    '%s;%s',
×
235
                    (string) $this->getBuilder()->getConfig()->get('baseurl'),
×
236
                    sprintf('http://%s:%s/', $host, $port)
×
237
                )
×
238
            );
×
239
        } catch (IOExceptionInterface $e) {
×
240
            throw new RuntimeException(sprintf('An error occurred while copying server\'s files to "%s"', $e->getPath()));
×
241
        }
242
        if (!is_file(Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'))) {
×
243
            throw new RuntimeException(sprintf('Router not found: "%s"', Util::joinFile(self::TMP_DIR, 'router.php')));
×
244
        }
245
    }
246

247
    /**
248
     * Removes temporary directory.
249
     *
250
     * @throws RuntimeException
251
     */
252
    public function tearDownServer(): void
253
    {
254
        $this->output->writeln('');
×
255
        $this->output->writeln('<comment>Server stopped.</comment>');
×
256

257
        try {
258
            Util\File::getFS()->remove(Util::joinFile($this->getPath(), self::TMP_DIR));
×
259
        } catch (IOExceptionInterface $e) {
×
260
            throw new RuntimeException($e->getMessage());
×
261
        }
262
    }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc