diff --git a/.editorconfig b/.editorconfig
index 27c8ac4..7b7dab7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,6 +8,6 @@ insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
-[*.{js,css,scss,yml,json,.babelrc}]
+[*.{js,css,scss,yml,json,.babelrc,.xml}]
indent_style = space
indent_size = 2
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
deleted file mode 100644
index 78fc770..0000000
--- a/.github/workflows/coding-standards.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Coding Standards
-
-on:
- push:
- branches:
- - develop
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-jobs:
- coding-standards:
- uses: alleyinteractive/.github/.github/workflows/php-coding-standards.yml@main
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..136d32b
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,64 @@
+name: Testing Suite
+
+on:
+ push:
+ branches:
+ - develop
+ pull_request:
+ schedule:
+ - cron: '0 0 * * *'
+
+jobs:
+ pr-tests:
+ # Don't run on draft PRs
+ if: github.event.pull_request.draft == false
+ # Timeout after 10 minutes
+ timeout-minutes: 10
+ # Define a matrix of PHP/WordPress versions to test against
+ strategy:
+ fail-fast: false
+ matrix:
+ php: [8.2, 8.3]
+ wordpress: ["latest"]
+ runs-on: ubuntu-latest
+ # Cancel any existing runs of this workflow
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}-P${{ matrix.php }}-WP${{ matrix.wordpress }}
+ cancel-in-progress: true
+ # Name the job in the matrix
+ name: "PR Tests PHP ${{ matrix.php }} WordPress ${{ matrix.wordpress }}"
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Run General Tests
+ # See https://github.com/alleyinteractive/action-test-general for more options
+ uses: alleyinteractive/action-test-general@develop
+
+ - name: Run PHP Tests
+ # See https://github.com/alleyinteractive/action-test-php for more options
+ uses: alleyinteractive/action-test-php@develop
+ with:
+ php-version: '${{ matrix.php }}'
+ wordpress-version: '${{ matrix.wordpress }}'
+ skip-wordpress-install: 'true'
+
+ # This required job ensures that all PR checks have passed before merging.
+ all-pr-checks-passed:
+ name: All PR checks passed
+ needs:
+ - pr-tests
+ runs-on: ubuntu-latest
+ if: always()
+ steps:
+ - name: Check job statuses
+ run: |
+ if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
+ echo "One or more jobs failed"
+ exit 1
+ elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
+ echo "One or more jobs were cancelled"
+ exit 1
+ else
+ echo "All jobs passed or were skipped"
+ exit 0
+ fi
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
deleted file mode 100644
index 882166a..0000000
--- a/.github/workflows/unit-test.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: Testing Suite
-
-on:
- push:
- branches:
- - develop
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-jobs:
- php-tests:
- strategy:
- matrix:
- php: [8.0]
- wordpress: ["latest"]
- uses: alleyinteractive/.github/.github/workflows/php-tests.yml@main
- with:
- php: ${{ matrix.php }}
- wordpress: ${{ matrix.wordpress }}
diff --git a/.github/workflows/upload-phar.yml b/.github/workflows/upload-phar.yml
new file mode 100644
index 0000000..3c19b33
--- /dev/null
+++ b/.github/workflows/upload-phar.yml
@@ -0,0 +1,33 @@
+name: Upload Phar to Release
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ tools: composer, box
+
+ - name: Install Composer dependencies
+ run: composer install
+
+ - name: Build Phar
+ run: composer build
+
+ # TODO: Add tests here
+
+ - name: Release
+ uses: softprops/action-gh-release@v2
+ if: startsWith(github.ref, 'refs/tags/')
+ with:
+ files: |
+ build/mantle-installer.phar
diff --git a/.gitignore b/.gitignore
index 4dd6deb..a324d64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-/vendor/
+.DS_Store
+.phpunit.result.cache
.vscode/
+/vendor/
+build
composer.lock
-.phpunit.result.cache
-.DS_Store
diff --git a/README.md b/README.md
index b342641..f2cb4c6 100644
--- a/README.md
+++ b/README.md
@@ -2,5 +2,4 @@
Documentation can be found inside the [Mantle documentation](https://mantle.alley.co/).
-
-
+[](https://github.com/alleyinteractive/mantle-installer/actions/workflows/tests.yml)
diff --git a/bin/mantle b/bin/mantle
index 57a1c91..b8a83f6 100755
--- a/bin/mantle
+++ b/bin/mantle
@@ -7,7 +7,7 @@ if ( file_exists( __DIR__ . '/../../../autoload.php' ) ) {
require __DIR__ . '/../vendor/autoload.php';
}
-$app = new Symfony\Component\Console\Application( 'Mantle Installer', '1.0.5' );
-$app->add( new Mantle\Installer\Console\Install_Command() );
+$app = new Symfony\Component\Console\Application( 'Mantle Installer', '1.1.0' );
+$app->add( new \Mantle\Installer\InstallCommand() );
$app->run();
diff --git a/bin/wp-cli.phar b/bin/wp-cli.phar
deleted file mode 100755
index 514ba41..0000000
Binary files a/bin/wp-cli.phar and /dev/null differ
diff --git a/box.json b/box.json
new file mode 100644
index 0000000..091ead2
--- /dev/null
+++ b/box.json
@@ -0,0 +1,12 @@
+{
+ "alias": "mantle-installer.phar",
+ "check-requirements": false,
+ "chmod": "0700",
+ "exclude-composer-files": false,
+ "directories": [
+ "bin",
+ "src",
+ "vendor"
+ ],
+ "output": "build/mantle-installer.phar"
+}
diff --git a/composer.json b/composer.json
index e9e1f2d..8296c46 100644
--- a/composer.json
+++ b/composer.json
@@ -9,31 +9,38 @@
}
],
"require": {
- "php": "^8.0",
- "symfony/console": "^5.0",
- "symfony/process": "^5.0"
+ "php": "^8.2",
+ "symfony/console": "^7.0",
+ "symfony/process": "^7.0"
},
"require-dev": {
- "alleyinteractive/alley-coding-standards": "^1.0",
- "phpunit/phpunit": "^9.3"
+ "alleyinteractive/alley-coding-standards": "^2.0",
+ "phpunit/phpunit": "^10.0",
+ "symfony/var-dumper": "^7.0"
},
- "config": {
- "sort-packages": true,
- "allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "Mantle\\Installer\\": "src/"
}
},
- "autoload": {
- "files": [
- "src/class-install-command.php"
- ]
+ "autoload-dev": {
+ "psr-4": {
+ "Mantle\\Installer\\Tests\\": "tests/"
+ }
},
- "minimum-stability": "dev",
- "prefer-stable": true,
"bin": [
"bin/mantle"
],
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "sort-packages": true
+ },
"scripts": {
+ "build": "box compile",
"phpcs": "phpcs --standard=./phpcs.xml .",
"phpunit": "phpunit",
"test": [
diff --git a/phpcs.xml b/phpcs.xml
index b8860d5..2f7d805 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -8,5 +8,7 @@
+
+
diff --git a/phpunit.xml b/phpunit.xml
index aa6af59..b7d93f9 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,14 +1,18 @@
+
-
-
- tests/test-install-command.php
-
-
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
+ bootstrap="vendor/autoload.php"
+ backupGlobals="false"
+ colors="true"
+ failOnDeprecation="true"
+ failOnNotice="true"
+ failOnWarning="true"
+ failOnPhpunitDeprecation="true"
+>
+
+
+ tests/InstallCommandTest.php
+
+
diff --git a/src/class-install-command.php b/src/InstallCommand.php
similarity index 80%
rename from src/class-install-command.php
rename to src/InstallCommand.php
index b8bb07e..5ac9fbb 100644
--- a/src/class-install-command.php
+++ b/src/InstallCommand.php
@@ -1,11 +1,11 @@
addOption( 'install', 'i', InputOption::VALUE_NONE, 'Install WordPress in the current location if it doesn\'t exist.' )
->addOption( 'no-must-use', 'no-mu', InputOption::VALUE_OPTIONAL, 'Don\'t load Mantle as a must-use plugin.', false )
->addOption( 'dev', 'd', InputOption::VALUE_NONE, 'Setup mantle for development on the framework.' )
- ->addOption( 'mantle-version', null, InputOption::VALUE_OPTIONAL, 'Version of alleyinteractive/mantle to install.', 'latest' );
+ ->addOption( 'mantle-version', null, InputOption::VALUE_OPTIONAL, 'Version of alleyinteractive/mantle to install.' );
}
/**
@@ -41,6 +48,8 @@ protected function configure() {
* @return int
*/
protected function execute( InputInterface $input, OutputInterface $output ): int {
+ $this->style = new SymfonyStyle( $input, $output );
+
$output->write(
PHP_EOL .
"
@@ -54,7 +63,7 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
if ( $this->check_if_hiring() ) {
$output->write(
- "Alley is hiring! Apply today at https://alley.co/careers/>. \n\n"
+ "Alley is hiring! Apply today at https://alley.com/careers/>. \n\n"
);
}
@@ -65,9 +74,7 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
return static::FAILURE;
}
- $this->install_mantle( $wordpress_root, $input, $output );
-
- return static::SUCCESS;
+ return $this->install_mantle( $wordpress_root, $input, $output );
}
/**
@@ -94,7 +101,15 @@ protected function get_wordpress_root( InputInterface $input, OutputInterface $o
}
}
- // Check if we are inside of the default Homestead WordPress environement.
+ // If the current directory is 'wp-content', use the parent directory.
+ if ( 'wp-content' === basename( $abspath ) && is_dir( dirname( $abspath ) . '/wp-includes' ) ) {
+ $abspath = dirname( $abspath );
+
+ $output->writeln( "Using [{$abspath}] as the WordPress installation." );
+ return $abspath;
+ }
+
+ // Check if we are inside of the default Homestead WordPress environment.
if (
is_dir( $abspath . '/wp-content/' ) &&
is_dir( $abspath . '/wp/' ) &&
@@ -104,24 +119,22 @@ protected function get_wordpress_root( InputInterface $input, OutputInterface $o
return $abspath;
}
- $style = new SymfonyStyle( $input, $output );
-
// Bail if the folder already exists.
if ( $name && is_dir( $name ) && ! $input->getOption( 'force' ) ) {
- $style->error( "Directory already exists: [{$abspath}] Use --force to override." );
+ $this->style->error( "Directory already exists: [{$abspath}] Use --force to override." );
return null;
}
// Ask the user if we should be installing if not already specified.
if (
$input->getOption( 'install' )
- || $style->confirm( "Would you like to install WordPress at [{$abspath}]", true )
+ || $this->style->confirm( "Would you like to install WordPress at [{$abspath}]", true )
) {
$this->install_wordpress( $abspath, $input, $output );
return $abspath;
}
- return $style->ask(
+ return $this->style->ask(
'Please specify your WordPress installation:',
null,
/**
@@ -157,7 +170,14 @@ function ( $dir ) {
protected function install_wordpress( string $dir, InputInterface $input, OutputInterface $output ): bool {
$output->writeln( "Installing WordPress at {$dir}>...\n\n" );
- $process = $this->run_commands( [ $this->find_wp_cli() . ' core download --force --path=' . $dir ], $input, $output );
+ $commands = [
+ 'mkdir /tmp/mantle-installer || true',
+ 'curl --clobber -o /tmp/mantle-installer/wordpress-latest.tar.gz https://wordpress.org/latest.tar.gz',
+ "mkdir -p {$dir} || true",
+ "tar --strip-components=1 -zxmf /tmp/mantle-installer/wordpress-latest.tar.gz -C {$dir}",
+ ];
+
+ $process = $this->run_commands( $commands, $input, $output );
if ( ! $process->isSuccessful() ) {
throw new RuntimeException( 'Error downloading WordPress: ' . $process->getExitCodeText() );
@@ -166,38 +186,6 @@ protected function install_wordpress( string $dir, InputInterface $input, Output
return true;
}
- /**
- * Find wp-cli.
- *
- * @return string
- */
- protected function find_wp_cli(): string {
- // Check if the wp-cli path was set in an environment variable.
- if ( $wp_cli = getenv( 'WP_CLI_PATH' ) ) {
- return $wp_cli;
- }
-
- $path = getcwd() . '/wp-cli.phar';
-
- if ( file_exists( $path ) ) {
- return '"' . PHP_BINARY . '" ' . $path;
- }
-
- // Check if wp-cli is installed globally.
- $path = exec( 'which wp' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_exec
-
- if ( $path ) {
- return $path;
- }
-
- // Fallback to the one installed with the package.
- if ( file_exists( __DIR__ . '/../bin/wp-cli.phar' ) ) {
- return '"' . PHP_BINARY . '" ' . __DIR__ . '/../bin/wp-cli.phar';
- }
-
- return 'wp';
- }
-
/**
* Get the composer command for the environment.
*
@@ -253,21 +241,36 @@ function ( $type, $line ) use ( $output ) {
*
* @throws RuntimeException Thrown on error.
*/
- protected function install_mantle( string $dir, InputInterface $input, OutputInterface $output ) {
+ protected function install_mantle( string $dir, InputInterface $input, OutputInterface $output ): int {
$wp_content = $dir . '/wp-content';
+ $name = $input->getArgument( 'name' )[0] ?? null;
+
+ if ( ! $name ) {
+ $name = 'mantle';
+
+ // Confirm the argument 'name' if not passed before assuming a default.
+ if ( ! $this->style->confirm( "No named was passed to the installer. Do you want to install Mantle at {$wp_content}/plugins/{$name}>", true ) ) {
+ $this->style->error( 'Mantle installation aborted. To specific a name, pass it as an argument: `mantle new my-plugin`.' );
+
+ return self::FAILURE;
+ }
+ }
- $name = $input->getArgument( 'name' )[0] ?? 'mantle';
$mantle_dir = "{$wp_content}/plugins/{$name}";
$framework_dir = "{$wp_content}/plugins/{$name}-framework";
// Check if Mantle exists at the current location.
if ( is_dir( $mantle_dir ) && file_exists( $mantle_dir . '/composer.json' ) ) {
- throw new RuntimeException( "Mantle is already installed: [{$mantle_dir}]" );
+ $this->style->error( "Mantle is already installed: [{$mantle_dir}]" );
+
+ return self::FAILURE;
}
// Check if the directory is empty.
if ( is_dir( $mantle_dir ) && count( scandir( $mantle_dir ) ) > 2 ) {
- throw new RuntimeException( "Directory is not empty: [{$mantle_dir}]" );
+ $this->style->error( "Directory is not empty: [{$mantle_dir}]" );
+
+ return self::FAILURE;
}
$version = $input->getOption( 'mantle-version' ) ? ':' . $input->getOption( 'mantle-version' ) : '';
@@ -280,7 +283,9 @@ protected function install_mantle( string $dir, InputInterface $input, OutputInt
// Setup the application for local development on the framework.
if ( $input->getOption( 'dev' ) ) {
if ( is_dir( $framework_dir ) && file_exists( "{$framework_dir}/composer.json" ) ) {
- throw new RuntimeException( "Mantle Framework is already installed: [{$framework_dir}'" );
+ $this->style->error( "Mantle Framework is already installed: [{$framework_dir}'" );
+
+ return self::FAILURE;
}
$commands = [
@@ -301,7 +306,9 @@ protected function install_mantle( string $dir, InputInterface $input, OutputInt
$process = $this->run_commands( $commands, $input, $output );
if ( ! $process->isSuccessful() ) {
- throw new RuntimeException( 'Error installing Mantle: ' . $process->getExitCodeText() );
+ $this->style->error( 'Error installing Mantle: ' . $process->getExitCodeText() );
+
+ return self::FAILURE;
}
$output->writeln( "Mantle installed successfully at {$mantle_dir}>." );
@@ -324,15 +331,21 @@ protected function install_mantle( string $dir, InputInterface $input, OutputInt
$mu_plugin = "{$mu_plugins}/{$name}-loader.php";
if ( file_exists( $mu_plugin ) ) {
- throw new RuntimeException( "Mantle MU Plugin loader already exists: [{$mu_plugin}]" );
+ $this->style->error( "Mantle MU Plugin loader already exists: [{$mu_plugin}]" );
+
+ return self::FAILURE;
}
if ( false === file_put_contents( $mu_plugin, trim( $this->get_mu_plugin_loader( $name ) ) ) ) { // phpcs:ignore
- throw new RuntimeException( "Error writing must-use plugin loader: [{$mu_plugin}]" );
+ $this->style->error( "Error writing must-use plugin loader: [{$mu_plugin}]" );
+
+ return self::FAILURE;
}
$output->writeln( "Must-use plugin created: {$mu_plugin}>" );
}
+
+ return self::SUCCESS;
}
/**
diff --git a/tests/test-install-command.php b/tests/InstallCommandTest.php
similarity index 88%
rename from tests/test-install-command.php
rename to tests/InstallCommandTest.php
index 9fa7c7d..6e439be 100644
--- a/tests/test-install-command.php
+++ b/tests/InstallCommandTest.php
@@ -1,13 +1,13 @@
assertFileExists( "{$output}/new-site/wp-content/mu-plugins/new-site-loader.php" );
}
- public function test_install_wordpress_dev() {
+ public function test_install_wordpress_dev(): void {
$output = __DIR__ . '/output';
chdir( $output );
@@ -87,7 +87,7 @@ public function test_install_wordpress_dev() {
protected function get_tester(): CommandTester {
$app = new Application( 'Mantle Installer' );
- $app->add( new Install_Command() );
+ $app->add( new InstallCommand() );
return new CommandTester( $app->find( 'new' ) );
}