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

orchestral / workbench / 12271587015

11 Dec 2024 07:24AM UTC coverage: 92.243% (+0.2%) from 92.02%
12271587015

push

github

web-flow
Merge pull request #60 from orchestral/8/prompt-namespace-prefix

* [8.x] Prompts to configure generated namespace

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

* wip

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

* wip

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

* wip

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

* wip

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

* wip

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

---------

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

1 of 1 new or added line in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

547 of 593 relevant lines covered (92.24%)

14.58 hits per line

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

86.45
/src/Console/DevToolCommand.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\Arr;
10
use Illuminate\Support\Collection;
11
use Orchestra\Testbench\Foundation\Console\Actions\EnsureDirectoryExists;
12
use Orchestra\Testbench\Foundation\Console\Actions\GeneratesFile;
13
use Orchestra\Workbench\Actions\DumpComposerAutoloads;
14
use Orchestra\Workbench\Actions\ModifyComposer;
15
use Orchestra\Workbench\Events\InstallEnded;
16
use Orchestra\Workbench\Events\InstallStarted;
17
use Orchestra\Workbench\Workbench;
18
use Symfony\Component\Console\Attribute\AsCommand;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22

23
use function Laravel\Prompts\confirm;
24
use function Orchestra\Testbench\join_paths;
25
use function Orchestra\Testbench\package_path;
26

27
#[AsCommand(name: 'workbench:devtool', description: 'Configure Workbench for package development')]
28
class DevToolCommand extends Command implements PromptsForMissingInput
29
{
30
    /**
31
     * Execute the console command.
32
     *
33
     * @return int
34
     */
35
    public function handle(Filesystem $filesystem)
36
    {
37
        $workingPath = package_path();
15✔
38

39
        event(new InstallStarted($this->input, $this->output, $this->components));
15✔
40

41
        $this->prepareWorkbenchDirectories($filesystem, $workingPath);
15✔
42
        $this->prepareWorkbenchNamespaces($filesystem, $workingPath);
15✔
43

44
        if ($this->option('install') === true) {
15✔
45
            $this->call('workbench:install', [
8✔
46
                '--force' => $this->option('force'),
8✔
47
                '--no-devtool' => true,
8✔
48
                '--basic' => $this->option('basic'),
8✔
49
            ]);
8✔
50
        }
51

52
        return tap(Command::SUCCESS, function ($exitCode) use ($workingPath) {
15✔
53
            event(new InstallEnded($this->input, $this->output, $this->components, $exitCode));
15✔
54

55
            (new DumpComposerAutoloads($workingPath))->handle();
15✔
56
        });
15✔
57
    }
58

59
    /**
60
     * Prepare workbench directories.
61
     */
62
    protected function prepareWorkbenchDirectories(Filesystem $filesystem, string $workingPath): void
63
    {
64
        $workbenchWorkingPath = join_paths($workingPath, 'workbench');
15✔
65

66
        (new EnsureDirectoryExists(
15✔
67
            filesystem: $filesystem,
15✔
68
            components: $this->components,
15✔
69
        ))->handle(
15✔
70
            Collection::make([
15✔
71
                join_paths('app', 'Models'),
15✔
72
                join_paths('database', 'factories'),
15✔
73
                join_paths('database', 'migrations'),
15✔
74
                join_paths('database', 'seeders'),
15✔
75
            ])->when(
15✔
76
                $this->option('basic') === false,
15✔
77
                fn ($directories) => $directories->push(...['routes', join_paths('resources', 'views')])
15✔
78
            )->map(static fn ($directory) => join_paths($workbenchWorkingPath, $directory))
15✔
79
        );
15✔
80

81
        $this->callSilently('make:provider', [
15✔
82
            'name' => 'WorkbenchServiceProvider',
15✔
83
            '--preset' => 'workbench',
15✔
84
            '--force' => (bool) $this->option('force'),
15✔
85
        ]);
15✔
86

87
        $this->prepareWorkbenchDatabaseSchema($filesystem, $workbenchWorkingPath);
15✔
88

89
        if ($this->option('basic') === false) {
15✔
90
            foreach (['api', 'console', 'web'] as $route) {
11✔
91
                (new GeneratesFile(
11✔
92
                    filesystem: $filesystem,
11✔
93
                    components: $this->components,
11✔
94
                    force: (bool) $this->option('force'),
11✔
95
                ))->handle(
11✔
96
                    (string) Workbench::stubFile("routes.{$route}"),
11✔
97
                    join_paths($workbenchWorkingPath, 'routes', "{$route}.php")
11✔
98
                );
11✔
99
            }
100
        }
101
    }
102

103
    /**
104
     * Prepare workbench namespace to `composer.json`.
105
     */
106
    protected function prepareWorkbenchNamespaces(Filesystem $filesystem, string $workingPath): void
107
    {
108
        (new ModifyComposer($workingPath))
15✔
109
            ->handle(fn (array $content) => $this->appendScriptsToComposer(
15✔
110
                $this->appendAutoloadDevToComposer($content, $filesystem), $filesystem
15✔
111
            ));
15✔
112
    }
113

114
    /**
115
     * Prepare workbench database schema including user model, factory and seeder.
116
     */
117
    protected function prepareWorkbenchDatabaseSchema(Filesystem $filesystem, string $workingPath): void
118
    {
119
        $this->callSilently('make:user-model', [
15✔
120
            '--preset' => 'workbench',
15✔
121
            '--force' => (bool) $this->option('force'),
15✔
122
        ]);
15✔
123

124
        $this->callSilently('make:user-factory', [
15✔
125
            '--preset' => 'workbench',
15✔
126
            '--force' => (bool) $this->option('force'),
15✔
127
        ]);
15✔
128

129
        (new GeneratesFile(
15✔
130
            filesystem: $filesystem,
15✔
131
            components: $this->components,
15✔
132
            force: (bool) $this->option('force'),
15✔
133
        ))->handle(
15✔
134
            (string) Workbench::stubFile('seeders.database'),
15✔
135
            join_paths($workingPath, 'database', 'seeders', 'DatabaseSeeder.php')
15✔
136
        );
15✔
137

138
        if ($filesystem->exists(join_paths($workingPath, 'database', 'factories', 'UserFactory.php'))) {
15✔
139
            $filesystem->replaceInFile([
15✔
140
                'use Orchestra\Testbench\Factories\UserFactory;',
15✔
141
            ], [
15✔
142
                'use Workbench\Database\Factories\UserFactory;',
15✔
143
            ], join_paths($workingPath, 'database', 'seeders', 'DatabaseSeeder.php'));
15✔
144
        }
145
    }
146

147
    /**
148
     * Append `scripts` to `composer.json`.
149
     */
150
    protected function appendScriptsToComposer(array $content, Filesystem $filesystem): array
151
    {
152
        $hasScriptsSection = \array_key_exists('scripts', $content);
15✔
153
        $hasTestbenchDusk = InstalledVersions::isInstalled('orchestra/testbench-dusk');
15✔
154

155
        if (! $hasScriptsSection) {
15✔
156
            $content['scripts'] = [];
15✔
157
        }
158

159
        $postAutoloadDumpScripts = array_filter([
15✔
160
            '@clear',
15✔
161
            '@prepare',
15✔
162
            $hasTestbenchDusk ? '@dusk:install-chromedriver' : null,
15✔
163
        ]);
15✔
164

165
        if (! \array_key_exists('post-autoload-dump', $content['scripts'])) {
15✔
166
            $content['scripts']['post-autoload-dump'] = $postAutoloadDumpScripts;
15✔
167
        } else {
168
            $content['scripts']['post-autoload-dump'] = array_values(array_unique([
×
169
                ...$postAutoloadDumpScripts,
×
170
                ...Arr::wrap($content['scripts']['post-autoload-dump']),
×
171
            ]));
×
172
        }
173

174
        $content['scripts']['clear'] = '@php vendor/bin/testbench package:purge-skeleton --ansi';
15✔
175
        $content['scripts']['prepare'] = '@php vendor/bin/testbench package:discover --ansi';
15✔
176

177
        if ($hasTestbenchDusk) {
15✔
178
            $content['scripts']['dusk:install-chromedriver'] = '@php vendor/bin/dusk-updater detect --auto-update --ansi';
×
179
        }
180

181
        $content['scripts']['build'] = '@php vendor/bin/testbench workbench:build --ansi';
15✔
182
        $content['scripts']['serve'] = [
15✔
183
            'Composer\\Config::disableProcessTimeout',
15✔
184
            '@build',
15✔
185
            $hasTestbenchDusk && \defined('TESTBENCH_DUSK')
15✔
186
                ? '@php vendor/bin/testbench-dusk serve --ansi'
×
187
                : '@php vendor/bin/testbench serve --ansi',
15✔
188
        ];
15✔
189

190
        if (! \array_key_exists('lint', $content['scripts'])) {
15✔
191
            $lintScripts = [];
15✔
192

193
            if (InstalledVersions::isInstalled('laravel/pint')) {
15✔
194
                $lintScripts[] = '@php vendor/bin/pint --ansi';
15✔
195
            } elseif ($filesystem->isFile(Workbench::packagePath('pint.json'))) {
×
196
                $lintScripts[] = 'pint';
×
197
            }
198

199
            if (InstalledVersions::isInstalled('phpstan/phpstan')) {
15✔
200
                $lintScripts[] = '@php vendor/bin/phpstan analyse --verbose --ansi';
15✔
201
            }
202

203
            if (\count($lintScripts) > 0) {
15✔
204
                $content['scripts']['lint'] = $lintScripts;
15✔
205
            }
206
        }
207

208
        if (
209
            $filesystem->isFile(Workbench::packagePath('phpunit.xml'))
15✔
210
            || $filesystem->isFile(Workbench::packagePath('phpunit.xml.dist'))
15✔
211
        ) {
212
            if (! \array_key_exists('test', $content['scripts'])) {
×
213
                $content['scripts']['test'] = [
×
214
                    '@clear',
×
215
                    InstalledVersions::isInstalled('pestphp/pest')
×
216
                        ? '@php vendor/bin/pest'
×
217
                        : '@php vendor/bin/phpunit',
×
218
                ];
×
219
            }
220
        }
221

222
        return $content;
15✔
223
    }
224

225
    /**
226
     * Append `autoload-dev` to `composer.json`.
227
     */
228
    protected function appendAutoloadDevToComposer(array $content, Filesystem $filesystem): array
229
    {
230
        /** @var array{autoload-dev?: array{psr-4?: array<string, string>}} $content */
231
        if (! \array_key_exists('autoload-dev', $content)) {
15✔
232
            $content['autoload-dev'] = [];
15✔
233
        }
234

235
        /** @var array{autoload-dev: array{psr-4?: array<string, string>}} $content */
236
        if (! \array_key_exists('psr-4', $content['autoload-dev'])) {
15✔
237
            $content['autoload-dev']['psr-4'] = [];
15✔
238
        }
239

240
        $namespacePrefix = '';
15✔
241

242
        if (confirm('Prefix with `Workbench` namespace?', default: true)) {
15✔
UNCOV
243
            $namespacePrefix = 'Workbench\\';
×
244
        }
245

246
        $namespaces = [
15✔
247
            'workbench/app/' => $namespacePrefix.'App\\',
15✔
248
            'workbench/database/factories/' => $namespacePrefix.'Database\\Factories\\',
15✔
249
            'workbench/database/seeders/' => $namespacePrefix.'Database\\Seeders\\',
15✔
250
        ];
15✔
251

252
        $autoloads = array_flip($content['autoload-dev']['psr-4']);
15✔
253

254
        foreach ($namespaces as $path => $namespace) {
15✔
255
            if (! \array_key_exists($path, $autoloads)) {
15✔
256
                $content['autoload-dev']['psr-4'][$namespace] = $path;
15✔
257

258
                $this->components->task(\sprintf(
15✔
259
                    'Added [%s] for [%s] to Composer', $namespace, '/'.rtrim($path, '/')
15✔
260
                ));
15✔
261
            } else {
UNCOV
262
                $this->components->twoColumnDetail(
×
UNCOV
263
                    \sprintf('Composer already contains [%s] path assigned to [%s] namespace', '/'.rtrim($path, '/'), $autoloads[$path]),
×
UNCOV
264
                    '<fg=yellow;options=bold>SKIPPED</>'
×
UNCOV
265
                );
×
266
            }
267
        }
268

269
        return $content;
15✔
270
    }
271

272
    /**
273
     * Prompt the user for any missing arguments.
274
     *
275
     * @return void
276
     */
277
    protected function promptForMissingArguments(InputInterface $input, OutputInterface $output)
278
    {
279
        $install = null;
14✔
280

281
        if ($input->getOption('skip-install') === true) {
14✔
UNCOV
282
            $install = false;
×
283
        } elseif (\is_null($input->getOption('install'))) {
14✔
284
            $install = confirm('Run Workbench installation?', true);
1✔
285
        }
286

287
        if (! \is_null($install)) {
14✔
288
            $input->setOption('install', $install);
1✔
289
        }
290
    }
291

292
    /**
293
     * Get the console command options.
294
     *
295
     * @return array
296
     */
297
    protected function getOptions()
298
    {
299
        return [
15✔
300
            ['force', 'f', InputOption::VALUE_NONE, 'Overwrite any existing files'],
15✔
301
            ['install', null, InputOption::VALUE_NEGATABLE, 'Run Workbench installation'],
15✔
302
            ['basic', null, InputOption::VALUE_NONE, 'Workbench installation without discovers and routes'],
15✔
303

304
            /** @deprecated */
305
            ['skip-install', null, InputOption::VALUE_NONE, 'Skipped Workbench installation (deprecated)'],
15✔
306
        ];
15✔
307
    }
308
}
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

© 2025 Coveralls, Inc