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

orchestral / workbench / 35614997512

21 Sep 2026 02:51PM UTC coverage: 92.846% (+0.08%) from 92.767%
35614997512

push

github

crynobone
Merge branch '8.x' into 9.x

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

29 of 30 new or added lines in 3 files covered. (96.67%)

610 of 657 relevant lines covered (92.85%)

17.69 hits per line

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

88.89
/src/Console/InstallCommand.php
1
<?php
2

3
namespace Orchestra\Workbench\Console;
4

5
use Composer\InstalledVersions;
6
use Illuminate\Console\Command;
7
use Illuminate\Contracts\Console\PromptsForMissingInput;
8
use Illuminate\Filesystem\Filesystem;
9
use Illuminate\Support\Collection;
10
use Orchestra\Testbench\Foundation\Console\Actions\EnsureDirectoryExists;
11
use Orchestra\Testbench\Foundation\Console\Actions\GeneratesFile;
12
use Orchestra\Testbench\Foundation\Console\Actions\RunCommand;
13
use Orchestra\Workbench\StubRegistrar;
14
use Orchestra\Workbench\Workbench;
15
use Symfony\Component\Console\Attribute\AsCommand;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19

20
use function Laravel\Prompts\confirm;
21
use function Laravel\Prompts\select;
22
use function Orchestra\Sidekick\Filesystem\join_paths;
23
use function Orchestra\Testbench\package_path;
24

25
#[AsCommand(name: 'workbench:install', description: 'Setup Workbench for package development')]
26
class InstallCommand extends Command implements PromptsForMissingInput
27
{
28
    /**
29
     * The `testbench.yaml` default configuration file.
30
     */
31
    public static ?string $configurationBaseFile = null;
32

33
    /**
34
     * Determine if Package also uses Testbench Dusk.
35
     */
36
    protected ?bool $hasTestbenchDusk = null;
37

38
    /** {@inheritDoc} */
39
    #[\Override]
40
    protected function initialize(InputInterface $input, OutputInterface $output)
41
    {
42
        $this->hasTestbenchDusk = InstalledVersions::isInstalled('orchestra/testbench-dusk');
27 ✔
43

44
        parent::initialize($input, $output);
27 ✔
45
    }
46

47
    /**
48
     * Execute the console command.
49
     *
50
     * @return int
51
     */
52
    public function handle(Filesystem $filesystem)
53
    {
54
        /** @var bool $pretending */
55
        $pretending = $this->option('pretend');
27 ✔
56

57
        $runCommand = (new RunCommand(
27 ✔
58
            console: $this,
27 ✔
59
            components: $this->components,
27 ✔
60
            pretending: $pretending,
27 ✔
61
        ));
27 ✔
62

63
        if (! $this->option('skip-devtool')) {
27 ✔
64
            $devtool = match (true) {
27 ✔
65
                \is_bool($this->option('devtool')) => $this->option('devtool'),
27 ✔
66
                default => $this->components->confirm('Install Workbench DevTool?', true),
×
67
            };
27 ✔
68

69
            if ($devtool === true) {
27 ✔
70
                $runCommand->handle('workbench:devtool', [
8 ✔
71
                    '--force' => $this->option('force'),
8 ✔
72
                    '--no-install' => true,
8 ✔
73
                    '--basic' => $this->option('basic'),
8 ✔
74
                ]);
8 ✔
75
            }
76
        }
77

78
        $workingPath = package_path();
27 ✔
79

80
        $this->copyTestbenchConfigurationFile($filesystem, $workingPath, $pretending);
27 ✔
81
        $this->copyTestbenchDotEnvFile($filesystem, $workingPath, $pretending);
27 ✔
82
        $this->prepareWorkbenchDirectories($filesystem, $workingPath, $pretending);
27 ✔
83

84
        $this->replaceDefaultLaravelSkeletonInTestbenchConfigurationFile($filesystem, $workingPath, $pretending);
27 ✔
85

86
        $runCommand->handle('workbench:create-sqlite-db', [
27 ✔
87
            '--force' => true,
27 ✔
88
        ]);
27 ✔
89

90
        return Command::SUCCESS;
27 ✔
91
    }
92

93
    /**
94
     * Prepare workbench directories.
95
     */
96
    protected function prepareWorkbenchDirectories(Filesystem $filesystem, string $workingPath, bool $pretending): void
97
    {
98
        if (! $this->input->isInteractive()) {
27 ✔
99
            return;
1 ✔
100
        }
101

102
        $workbenchWorkingPath = join_paths($workingPath, 'workbench');
26 ✔
103

104
        foreach (['app' => true, 'providers' => false] as $bootstrap => $default) {
26 ✔
105
            if (! confirm("Generate `workbench/bootstrap/{$bootstrap}.php` file?", default: $default)) {
26 ✔
106
                continue;
26 ✔
107
            }
108

109
            (new GeneratesFile(
×
110
                filesystem: $filesystem,
×
111
                components: $this->components,
×
112
                force: (bool) $this->option('force'),
×
NEW
113
                pretending: $pretending,
×
114
            ))->handle(
×
115
                (string) realpath(join_paths(__DIR__, 'stubs', 'bootstrap', "{$bootstrap}.php")),
×
116
                join_paths($workbenchWorkingPath, 'bootstrap', "{$bootstrap}.php")
×
117
            );
×
118
        }
119
    }
120

121
    /**
122
     * Copy the "testbench.yaml" file.
123
     */
124
    protected function copyTestbenchConfigurationFile(Filesystem $filesystem, string $workingPath, bool $pretending): void
125
    {
126
        $from = ! \is_null(static::$configurationBaseFile)
27 ✔
127
            ? (string) realpath(static::$configurationBaseFile)
×
128
            : (string) Workbench::stubFile($this->option('basic') === true ? 'config.basic' : 'config');
27 ✔
129

130
        $to = join_paths($workingPath, 'testbench.yaml');
27 ✔
131

132
        (new GeneratesFile(
27 ✔
133
            filesystem: $filesystem,
27 ✔
134
            components: $this->components,
27 ✔
135
            force: (bool) $this->option('force'),
27 ✔
136
            pretending: $pretending,
27 ✔
137
        ))->handle($from, $to);
27 ✔
138

139
        if ($pretending === false) {
27 ✔
140
            StubRegistrar::replaceInFile($filesystem, $to);
27 ✔
141
        }
142
    }
143

144
    /**
145
     * Copy the ".env" file.
146
     */
147
    protected function copyTestbenchDotEnvFile(Filesystem $filesystem, string $workingPath, bool $pretending): void
148
    {
149
        $workbenchWorkingPath = join_paths($workingPath, 'workbench');
27 ✔
150

151
        $from = $this->laravel->basePath('.env.example');
27 ✔
152

153
        if (! $filesystem->isFile($from)) {
27 ✔
154
            return;
×
155
        }
156

157
        /** @var \Illuminate\Support\Collection<int, string> $choices */
158
        $choices = (new Collection($this->environmentFiles()))
27 ✔
159
            ->reject(static fn ($file) => $filesystem->isFile(join_paths($workbenchWorkingPath, $file)))
27 ✔
160
            ->values();
27 ✔
161

162
        if (! $this->option('force') && $choices->isEmpty()) {
27 ✔
163
            $this->components->twoColumnDetail(
1 ✔
164
                'File [.env] already exists', '<fg=yellow;options=bold>SKIPPED</>'
1 ✔
165
            );
1 ✔
166

167
            return;
1 ✔
168
        }
169

170
        /** @var string|null $targetEnvironmentFile */
171
        $targetEnvironmentFile = $this->input->isInteractive()
26 ✔
172
            ? select(
26 ✔
173
                "Export '.env' file as?",
26 ✔
174
                $choices->prepend('Skip exporting .env'), // @phpstan-ignore argument.type
26 ✔
175
            ) : null;
26 ✔
176

177
        if (\in_array($targetEnvironmentFile, [null, 'Skip exporting .env'])) {
26 ✔
178
            return;
8 ✔
179
        }
180

181
        (new EnsureDirectoryExists(
18 ✔
182
            filesystem: $filesystem,
18 ✔
183
            pretending: $pretending,
18 ✔
184
        ))->handle([
18 ✔
185
            $workbenchWorkingPath,
18 ✔
186
        ]);
18 ✔
187

188
        $this->generateSeparateEnvironmentFileForTestbenchDusk($filesystem, $workbenchWorkingPath, $targetEnvironmentFile);
18 ✔
189

190
        (new GeneratesFile(
18 ✔
191
            filesystem: $filesystem,
18 ✔
192
            components: $this->components,
18 ✔
193
            force: (bool) $this->option('force'),
18 ✔
194
            pretending: $pretending,
18 ✔
195
        ))->handle(
18 ✔
196
            $from,
18 ✔
197
            join_paths($workbenchWorkingPath, $targetEnvironmentFile)
18 ✔
198
        );
18 ✔
199

200
        (new GeneratesFile(
18 ✔
201
            filesystem: $filesystem,
18 ✔
202
            force: (bool) $this->option('force'),
18 ✔
203
            pretending: $pretending,
18 ✔
204
        ))->handle(
18 ✔
205
            (string) Workbench::stubFile('gitignore'),
18 ✔
206
            join_paths($workbenchWorkingPath, '.gitignore')
18 ✔
207
        );
18 ✔
208
    }
209

210
    /**
211
     * Replace the default `laravel` skeleton for Testbench Dusk.
212
     *
213
     * @codeCoverageIgnore
214
     */
215
    protected function replaceDefaultLaravelSkeletonInTestbenchConfigurationFile(
216
        Filesystem $filesystem,
217
        string $workingPath,
218
        bool $pretending,
219
    ): void {
220
        if ($this->hasTestbenchDusk === false || $pretending === true) {
221
            return;
222
        }
223

224
        $filesystem->replaceInFile(["laravel: '@testbench'"], ["laravel: '@testbench-dusk'"], join_paths($workingPath, 'testbench.yaml'));
225
    }
226

227
    /**
228
     * Generate separate `.env.dusk` equivalent for Testbench Dusk.
229
     *
230
     * @codeCoverageIgnore
231
     */
232
    protected function generateSeparateEnvironmentFileForTestbenchDusk(Filesystem $filesystem, string $workbenchWorkingPath, string $targetEnvironmentFile): void
233
    {
234
        if ($this->hasTestbenchDusk === false) {
235
            return;
236
        }
237

238
        if ($this->components->confirm('Create separate environment file for Testbench Dusk?', false)) {
239
            (new GeneratesFile(
240
                filesystem: $filesystem,
241
                components: $this->components,
242
                force: (bool) $this->option('force'),
243
                pretending: (bool) $this->option('pretend'),
244
            ))->handle(
245
                $this->laravel->basePath('.env.example'),
246
                join_paths($workbenchWorkingPath, str_replace('.env', '.env.dusk', $targetEnvironmentFile))
247
            );
248
        }
249
    }
250

251
    /**
252
     * Get possible environment files.
253
     *
254
     * @return array<int, string>
255
     */
256
    protected function environmentFiles(): array
257
    {
258
        return [
27 ✔
259
            '.env',
27 ✔
260
            '.env.example',
27 ✔
261
            '.env.dist',
27 ✔
262
        ];
27 ✔
263
    }
264

265
    /**
266
     * Prompt the user for any missing arguments.
267
     *
268
     * @return void
269
     */
270
    protected function promptForMissingArguments(InputInterface $input, OutputInterface $output)
271
    {
272
        $devtool = null;
26 ✔
273

274
        if ($input->getOption('skip-devtool') === true) {
26 ✔
275
            $devtool = false;
×
276
        } elseif (\is_null($input->getOption('devtool'))) {
26 ✔
277
            $devtool = confirm('Run Workbench DevTool installation?', true);
1 ✔
278
        }
279

280
        if (! \is_null($devtool)) {
26 ✔
281
            $input->setOption('devtool', $devtool);
1 ✔
282
        }
283
    }
284

285
    /**
286
     * Get the console command options.
287
     *
288
     * @return array
289
     */
290
    protected function getOptions()
291
    {
292
        return [
27 ✔
293
            ['force', 'f', InputOption::VALUE_NONE, 'Overwrite any existing files'],
27 ✔
294
            ['devtool', null, InputOption::VALUE_NEGATABLE, 'Run DevTool installation'],
27 ✔
295
            ['basic', null, InputOption::VALUE_NONE, 'Skipped routes and discovers installation'],
27 ✔
296
            ['pretend', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything'],
27 ✔
297

298
            /** @deprecated */
299
            ['skip-devtool', null, InputOption::VALUE_NONE, 'Skipped DevTool installation (deprecated)'],
27 ✔
300
        ];
27 ✔
301
    }
302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc