Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.30% covered (success)
91.30%
21 / 23
75.00% covered (success)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Asset
91.30% covered (success)
91.30%
21 / 23
75.00% covered (success)
75.00%
6 / 8
13.11
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isInstalled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNpmAvailable
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 install
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 watch
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 touchAssetMarkers
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 run
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/pop-bootstrap
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Kettle\Model;
16
17use Pop\Utils\AbstractModel;
18
19/**
20 * Console asset model class
21 *
22 * @category   Pop\Kettle
23 * @package    Pop\Kettle
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    3.0.0
28 */
29class Asset extends AbstractModel
30{
31
32    /**
33     * npm binary to invoke
34     * @var string
35     */
36    protected string $npmBinary = 'npm';
37
38    /**
39     * Constructor
40     *
41     * @param string $npmBinary
42     */
43    public function __construct(string $npmBinary = 'npm')
44    {
45        parent::__construct();
46        $this->npmBinary = $npmBinary;
47    }
48
49    /**
50     * Whether a package.json exists at the given location
51     *
52     * @param  string $location
53     * @return bool
54     */
55    public function isInstalled(string $location): bool
56    {
57        return file_exists($location . DIRECTORY_SEPARATOR . 'package.json');
58    }
59
60    /**
61     * Whether the configured npm binary is available on the PATH
62     *
63     * @return bool
64     */
65    public function isNpmAvailable(): bool
66    {
67        if (PHP_OS_FAMILY === 'Windows') {
68            exec('where ' . escapeshellarg($this->npmBinary), $output, $exitCode);
69        } else {
70            exec('command -v ' . escapeshellarg($this->npmBinary) . ' 2>/dev/null', $output, $exitCode);
71        }
72
73        return ($exitCode === 0) && !empty($output);
74    }
75
76    /**
77     * Run `npm install` at the given location
78     *
79     * @param  string $location
80     * @return int
81     */
82    public function install(string $location): int
83    {
84        return $this->run([$this->npmBinary, 'install'], $location);
85    }
86
87    /**
88     * Run `npm run watch` at the given location
89     *
90     * @param  string $location
91     * @return int
92     */
93    public function watch(string $location): int
94    {
95        $result = $this->run([$this->npmBinary, 'run', 'watch'], $location);
96        $this->touchAssetMarkers($location);
97        return $result;
98    }
99
100    /**
101     * Run `npm run build` at the given location
102     *
103     * @param  string $location
104     * @return int
105     */
106    public function build(string $location): int
107    {
108        $result = $this->run([$this->npmBinary, 'run', 'build'], $location);
109        $this->touchAssetMarkers($location);
110        return $result;
111    }
112
113    /**
114     * Re-create the .empty marker files in the build output folders, which
115     * vite's build step wipes out along with the rest of the outDir contents
116     * on every rebuild, so the otherwise-empty folders still commit to Git
117     *
118     * @param  string $location
119     * @return void
120     */
121    protected function touchAssetMarkers(string $location): void
122    {
123        foreach (['css', 'js'] as $type) {
124            $dir = $location . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . $type;
125            if (!is_dir($dir)) {
126                mkdir($dir, 0777, true);
127            }
128            touch($dir . DIRECTORY_SEPARATOR . '.empty');
129        }
130    }
131
132    /**
133     * Run a command with inherited stdio, returning its exit code
134     *
135     * @param  array  $command
136     * @param  string $cwd
137     * @return int
138     */
139    protected function run(array $command, string $cwd): int
140    {
141        $process = proc_open($command, [0 => STDIN, 1 => STDOUT, 2 => STDERR], $pipes, $cwd);
142        if (!is_resource($process)) {
143            return 1;
144        }
145        return proc_close($process);
146    }
147
148}