Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/Enums/NodePackageManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Laravel\Installer\Console\Enums;

enum NodePackageManager: string
{
case NPM = 'npm';
case YARN = 'yarn';
case PNPM = 'pnpm';
case BUN = 'bun';

public function installCommand(): string
{
return match ($this) {
self::NPM => 'npm install',
self::YARN => 'yarn install',
self::PNPM => 'pnpm install',
self::BUN => 'bun install',
};
}

public function runCommand(): string
{
return match ($this) {
self::NPM => 'npm run',
self::YARN => 'yarn',
self::PNPM => 'pnpm',
self::BUN => 'bun run',
};
}

public function buildCommand(): string
{
return $this->runCommand().' build';
}

public function runLocalOrRemoteCommand(): string
{
return match ($this) {
self::NPM => 'npx',
self::YARN => 'npx',
self::PNPM => 'pnpm dlx',
self::BUN => 'npx',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bunx is Bun's equivalent of npx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Opened a PR: #443

};
}

public static function allLockFiles(): array
{
return ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lock', 'bun.lockb'];
}

public function lockFiles(): array
{
return match ($this) {
self::NPM => ['package-lock.json'],
self::YARN => ['yarn.lock'],
self::PNPM => ['pnpm-lock.yaml'],
self::BUN => ['bun.lock', 'bun.lockb'],
};
}
}
102 changes: 75 additions & 27 deletions src/NewCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Support\Composer;
use Illuminate\Support\ProcessUtils;
use Illuminate\Support\Str;
use Laravel\Installer\Console\Enums\NodePackageManager;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
Expand Down Expand Up @@ -64,6 +65,9 @@ protected function configure()
->addOption('pest', null, InputOption::VALUE_NONE, 'Install the Pest testing framework')
->addOption('phpunit', null, InputOption::VALUE_NONE, 'Install the PHPUnit testing framework')
->addOption('npm', null, InputOption::VALUE_NONE, 'Install and build NPM dependencies')
->addOption('pnpm', null, InputOption::VALUE_NONE, 'Install and build NPM dependencies via PNPM')
->addOption('bun', null, InputOption::VALUE_NONE, 'Install and build NPM dependencies via Bun')
->addOption('yarn', null, InputOption::VALUE_NONE, 'Install and build NPM dependencies via Yarn')
->addOption('using', null, InputOption::VALUE_OPTIONAL, 'Install a custom starter kit from a community maintained package')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces install even if the directory already exists');
}
Expand Down Expand Up @@ -499,43 +503,35 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln('');
}

$this->configureComposerDevScript($directory);
[$packageManager, $runPackageManager] = $this->determinePackageManager($directory, $input);

$this->configureComposerScripts($packageManager);

if ($input->getOption('pest')) {
$output->writeln('');
}

$packageInstall = 'npm install';
$packageBuild = 'npm run build';

if (file_exists($directory.'/pnpm-lock.yaml')) {
$packageInstall = 'pnpm install';
$packageBuild = 'pnpm run build';
} elseif (file_exists($directory.'/yarn.lock')) {
$packageInstall = 'yarn install';
$packageBuild = 'yarn run build';
} elseif (file_exists($directory.'/bun.lock')) {
$packageInstall = 'bun install';
$packageBuild = 'bun run build';
if (! $runPackageManager && $input->isInteractive()) {
$runPackageManager = confirm(
label: 'Would you like to run <options=bold>'.$packageManager->installCommand().'</> and <options=bold>'.$packageManager->buildCommand().'</>?'
);
}

$runNpm = $input->getOption('npm');

if (! $input->getOption('npm') && $input->isInteractive()) {
$runNpm = confirm(
label: 'Would you like to run <options=bold>'.$packageInstall.'</> and <options=bold>'.$packageBuild.'</>?'
);
foreach (NodePackageManager::allLockFiles() as $lockFile) {
if (! in_array($lockFile, $packageManager->lockFiles()) && file_exists($directory.'/'.$lockFile)) {
(new Filesystem())->delete($directory.'/'.$lockFile);
}
}

if ($runNpm) {
$this->runCommands([$packageInstall, $packageBuild], $input, $output, workingPath: $directory);
if ($runPackageManager) {
$this->runCommands([$packageManager->installCommand(), $packageManager->buildCommand()], $input, $output, workingPath: $directory);
}

$output->writeln(" <bg=blue;fg=white> INFO </> Application ready in <options=bold>[{$name}]</>. You can start your local development using:".PHP_EOL);
$output->writeln('<fg=gray>➜</> <options=bold>cd '.$name.'</>');

if (! $runNpm) {
$output->writeln('<fg=gray>➜</> <options=bold>'.$packageInstall.' && npm run build</>');
if (! $runPackageManager) {
$output->writeln('<fg=gray>➜</> <options=bold>'.$packageManager->installCommand().' && '.$packageManager->buildCommand().'</>');
}

if ($this->isParkedOnHerdOrValet($directory)) {
Expand All @@ -553,6 +549,48 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return $process->getExitCode();
}

/**
* Determine the Node package manager to use.
*
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @return array{NodePackageManager, bool}
*/
protected function determinePackageManager(string $directory, InputInterface $input): array
{
// If they passed a specific flag, respect the user's choice...
if ($input->getOption('pnpm')) {
return [NodePackageManager::PNPM, true];
}

if ($input->getOption('bun')) {
return [NodePackageManager::BUN, true];
}

if ($input->getOption('yarn')) {
return [NodePackageManager::YARN, true];
}

if ($input->getOption('npm')) {
return [NodePackageManager::NPM, true];
}

// Check for an existing lock file to determine the package manager...
foreach (NodePackageManager::cases() as $packageManager) {
if ($packageManager === NodePackageManager::NPM) {
continue;
}

foreach ($packageManager->lockFiles() as $lockFile) {
if (file_exists($directory.'/'.$lockFile)) {
return [$packageManager, false];
}
}
}

return [NodePackageManager::NPM, false];
}

/**
* Return the local machine's default Git branch if set or default to `main`.
*
Expand Down Expand Up @@ -920,21 +958,31 @@ protected function pushToGitHub(string $name, string $directory, InputInterface
}

/**
* Configure the Composer "dev" script.
* Configure the Composer scripts for the selected package manager.
*
* @param string $directory
* @param NodePackageManager $packageManager
* @return void
*/
protected function configureComposerDevScript(string $directory): void
protected function configureComposerScripts(NodePackageManager $packageManager): void
{
$this->composer->modify(function (array $content) {
$this->composer->modify(function (array $content) use ($packageManager) {
if (windows_os()) {
$content['scripts']['dev'] = [
'Composer\\Config::disableProcessTimeout',
"npx concurrently -c \"#93c5fd,#c4b5fd,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"npm run dev\" --names='server,queue,vite'",
];
}

foreach (['dev', 'dev:ssr', 'setup'] as $scriptKey) {
if (array_key_exists($scriptKey, $content['scripts'])) {
$content['scripts'][$scriptKey] = str_replace(
['npm', 'npx', 'ppnpm'],
[$packageManager->value, $packageManager->runLocalOrRemoteCommand(), 'pnpm'],
$content['scripts'][$scriptKey],
);
}
}

return $content;
});
}
Expand Down