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

orchestral / workbench / 35615453861

21 Sep 2026 02:55PM UTC coverage: 93.088% (+0.07%) from 93.016%
35615453861

push

github

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

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

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

606 of 651 relevant lines covered (93.09%)

17.84 hits per line

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

89.38
/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
        $devtool = match (true) {
27✔
64
            \is_bool($this->option('devtool')) => $this->option('devtool'),
27✔
65
            default => $this->components->confirm('Install Workbench DevTool?', true),
×
66
        };
27✔
67

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

76
        $workingPath = package_path();
27✔
77

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

82
        $this->replaceDefaultLaravelSkeletonInTestbenchConfigurationFile($filesystem, $workingPath, $pretending);
27✔
83

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

88
        return Command::SUCCESS;
27✔
89
    }
90

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

100
        $workbenchWorkingPath = join_paths($workingPath, 'workbench');
26✔
101

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

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

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

128
        $to = join_paths($workingPath, 'testbench.yaml');
27✔
129

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

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

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

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

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

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

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

165
            return;
1✔
166
        }
167

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

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

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

186
        $this->generateSeparateEnvironmentFileForTestbenchDusk($filesystem, $workbenchWorkingPath, $targetEnvironmentFile);
18✔
187

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

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

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

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

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

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

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

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

272
        if (\is_null($input->getOption('devtool'))) {
26✔
273
            $devtool = confirm('Run Workbench DevTool installation?', true);
1✔
274
        }
275

276
        if (! \is_null($devtool)) {
26✔
277
            $input->setOption('devtool', $devtool);
1✔
278
        }
279
    }
280

281
    /**
282
     * Get the console command options.
283
     *
284
     * @return array
285
     */
286
    protected function getOptions()
287
    {
288
        return [
27✔
289
            ['force', 'f', InputOption::VALUE_NONE, 'Overwrite any existing files'],
27✔
290
            ['devtool', null, InputOption::VALUE_NEGATABLE, 'Run DevTool installation'],
27✔
291
            ['basic', null, InputOption::VALUE_NONE, 'Skipped routes and discovers installation'],
27✔
292
            ['pretend', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything'],
27✔
293
        ];
27✔
294
    }
295
}
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