diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index fe9e60ec..6fc57523 100644
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -30,9 +30,10 @@ then
if [ $drupalfailed != 0 ]
then
echo "PHPCS failed, errors found not fixable automatically, git commit denied!"
+ exit 1
fi
fi
#### End of Code Sniffer
-exit $commitfailed
+exit 0
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 53188fbf..dc2d42a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -47,6 +47,7 @@ jobs:
runs-on: ubuntu-latest
env:
THEME_PATH: 'themes/health'
+ HEALTH_CI_MODE: 1
steps:
-
name: Checkout codebase
diff --git a/README.md b/README.md
index 9673361e..c8e46ad2 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ This documentation assumes you are using MacOS or Linux as your host environment
- [Docker](https://docs.docker.com/install/)
- [Pygmy](https://pygmy.readthedocs.io/en/master/)
- [Ahoy](https://github.com/ahoy-cli/ahoy)
+- [Yarn](https://yarnpkg.com/) (required if installing apps or tools built using Yarn)
## Create new project based on ASK
@@ -193,12 +194,14 @@ Add the app using Composer using the package name.
composer require healthgovau/out-of-pocket
```
-During the installation process you will be asked the following:
+During the installation process you will be asked one or more of the following questions:
| Question | Description |
| --- | --- |
-| Is this a production build? | Respond `n` to enable app console logging or if you want to use local dev version of the app (e.g. you want to run the app dev environment using `npm run start`). |
-| Remove static CSS links from project? | Will be asked when importing a React app. It is designed to support legacy apps which use the CSS stylesheets provided by the host site. Responding `y` will result in links to CSS files in `build/static/css` directory being removed from the app. This process effectively removes all CSS files compiled directly by the React app. The use of apps which are fully decoupled from the host site are strongly encouraged. In such case on would not remove static CSS links. Please consult with the app developer or app project manager if you are unclear whether or not these static CSS files should be included or not. |
+| In which theme should the app or tool be installed? | Enter the number corresponding to the theme in which the app or tool should be installed. |
+| Is this a production build? | Respond `n` to enable app console logging or if you want to use local dev version of the app (e.g. you want to run the app dev environment using `npm run start`). Default value is __yes__. |
+| Remove static CSS links from project? | Will be asked when importing a React app. The default value is __no__. It is designed to support legacy apps which use the CSS stylesheets provided by the host site. Responding `y` will result in links to CSS files in `build/static/css` directory being removed from the app. This process effectively removes all CSS files compiled directly by the React app. The use of apps which are fully decoupled from the host site are strongly encouraged. In such case on would not remove static CSS links. Please consult with the app developer or app project manager if you are unclear whether or not these static CSS files should be included or not. |
+| Which package manager would you like to use? | Enter the number of the package manager to use when building the app or tool project. The installer will attempt to detect whether npm or yarn is being used as the package manager. In the case that it detects configuration from both package managers it will ask you to confirm which one should be used. |
#### Update an app
@@ -473,7 +476,7 @@ To use:
The project makes use of [Cypress](https://www.cypress.io/) and [Percy](https://percy.io/) to implemented visual regression tests in Github workflow pipelines.
-When creating or updating pull requests on `develop` or `master` branches snapshots of sample content type pages will be created and analysed by Percy. In case of a failure the results can be reviewed in Percy. Once approved in Percy one will be able to merge the pull request.
+When creating or updating pull requests on the `v1.x` branch snapshots of sample content type pages will be created and analysed by Percy. In case of a failure the results can be reviewed in Percy. Once approved in Percy one will be able to merge the pull request.
Cypress test configuration can be found in the `cypress/integration/` directory.
diff --git a/apps/README.md b/apps/README.md
new file mode 100644
index 00000000..73f444e3
--- /dev/null
+++ b/apps/README.md
@@ -0,0 +1,3 @@
+# Temp directory
+
+This directory is used for temporary file storage as part of the app integration process. Files in this dictory should not be commited to the project repository.
\ No newline at end of file
diff --git a/apps/apps.json b/apps/apps.json
new file mode 100644
index 00000000..2b029d59
--- /dev/null
+++ b/apps/apps.json
@@ -0,0 +1 @@
+[{"app":"sample-react-app","theme":"health"}]
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 6795f3ab..a15a8503 100644
--- a/composer.json
+++ b/composer.json
@@ -16,8 +16,17 @@
},
"repositories": [
{
- "type": "composer",
- "url": "https://packages.drupal.org/8"
+ "type": "package",
+ "package": {
+ "name": "healthgovau/out-of-pocket",
+ "version": "1.2.1",
+ "type": "health-app-react",
+ "source": {
+ "type": "git",
+ "url": "git@github.com:healthgovau/out-of-pocket.git",
+ "reference": "v1.2.1"
+ }
+ }
},
{
"type": "package",
@@ -44,6 +53,10 @@
"reference": "1.0.1"
}
}
+ },
+ {
+ "type": "composer",
+ "url": "https://packages.drupal.org/8"
}
],
"minimum-stability": "stable",
@@ -60,12 +73,13 @@
},
"scripts": {
"post-install-cmd": [
- "cat .githooks/pre-commit >> .git/hooks/pre-commit && chmod a+x .git/hooks/pre-commit"
+ "cat .githooks/pre-commit > .git/hooks/pre-commit && chmod a+x .git/hooks/pre-commit"
],
"post-package-install": [
"Health\\Helper::postPackageInstall",
"Health\\App::postPackageInstall"
],
+ "post-package-uninstall": "Health\\App::postPackageUninstall",
"code-check": "./vendor/bin/phpcs",
"code-fix": "./vendor/bin/phpcbf"
},
@@ -81,7 +95,7 @@
"type:health-theme",
"type:drupal-theme"
],
- "themes/health/apps/{$name}/": [
+ "apps/{$name}/": [
"type:health-app-default",
"type:health-app-react",
"type:health-app-angular"
diff --git a/cypress/integration/modules/app_or_tool_hosted.js b/cypress/integration/modules/app_or_tool_hosted.js
index 149e8f35..ce2d5038 100644
--- a/cypress/integration/modules/app_or_tool_hosted.js
+++ b/cypress/integration/modules/app_or_tool_hosted.js
@@ -14,15 +14,9 @@
it("renders correctly", () => {
// Take a snapshot for visual diffing.
const percyOptions = {};
- cy.visit(landingPageUri);
- cy.get('.ButtonPanel_component-button-panel__3doTu button').contains('7').click();
- cy.get('.ButtonPanel_component-button-panel__3doTu button').contains('x').click();
- cy.get('.ButtonPanel_component-button-panel__3doTu button').contains('9').click();
- cy.get('.ButtonPanel_component-button-panel__3doTu button').contains('=').click();
- cy.get('.Display_component-display__T1qck div').should('contain', 63)
- .then(() => {
- cy.percySnapshot("health_starter_kit_sample_app_or_tool_content_page_hosted", percyOptions);
- });
+ cy
+ .visit(landingPageUri)
+ .percySnapshot("health_starter_kit_sample_app_or_tool_content_page_hosted", percyOptions);
});
});
}
diff --git a/phpcs.xml b/phpcs.xml
index 21325355..931edc1e 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -3,7 +3,7 @@
PHP CodeSniffer configuration for Drupal coding standards.
./dev_modules/custom
./themes/health
- ./themes/*/app/*
+ ./themes/*/apps/*
./scripts/*
diff --git a/src/Health/App.php b/src/Health/App.php
index 36e18526..5f68d81e 100644
--- a/src/Health/App.php
+++ b/src/Health/App.php
@@ -4,13 +4,30 @@
use Composer\Installer\PackageEvent;
use Composer\Package\CompletePackage;
-use DOMDocument;
/**
* Utility for integration Health apps into the Health website theme.
*/
class App {
+ const PACKAGE_MANAGER_NPM = 2;
+
+ const PACKAGE_MANAGER_YARN = 1;
+
+ /**
+ * Full path to app registry file.
+ *
+ * @var string
+ */
+ protected $appRegistryFile;
+
+ /**
+ * Is the script running in CI mode.
+ *
+ * @var bool
+ */
+ protected $ciMode = FALSE;
+
/**
* List of support app types.
*
@@ -30,12 +47,26 @@ class App {
protected $packageEvent;
/**
- * Package name
+ * The package manager used to control dependencies.
+ *
+ * @var string
+ */
+ protected $dependencyManager = 'npm';
+
+ /**
+ * Package name.
*
* @var string
*/
protected $packageName;
+ /**
+ * Path to root of ASK project.
+ *
+ * @var string
+ */
+ protected $projectPath;
+
/**
* Path to installed app.
*
@@ -57,6 +88,13 @@ class App {
*/
protected $consoleIO;
+ /**
+ * Name of theme in which app should be added to.
+ *
+ * @var string
+ */
+ protected $destinationTheme;
+
/**
* Defines if the app should be a production or development build.
*
@@ -71,6 +109,13 @@ class App {
*/
protected $removeCss;
+ /**
+ * The maching name of the theme in which to install the app.
+ *
+ * @var string
+ */
+ protected $targetTheme = '';
+
/**
* Constructor.
*/
@@ -81,8 +126,15 @@ public function __construct(PackageEvent $packageEvent) {
$package = $packageEvent->getOperation()->getPackage();
$installationManager = $packageEvent->getComposer()->getInstallationManager();
$relativeAppPath = $installationManager->getInstallPath($package);
- $projectPath = dirname($this->packageEvent->getComposer()->getConfig()->get('vendor-dir'));
- $this->appPath = $projectPath . '/' . $relativeAppPath;
+ $vendorDirectory = $this
+ ->packageEvent
+ ->getComposer()
+ ->getConfig()
+ ->get('vendor-dir');
+ $this->projectPath = dirname($vendorDirectory);
+ $this->relativeAppPath = $relativeAppPath;
+ $this->appPath = $this->projectPath . '/' . $relativeAppPath;
+ $this->appRegistryFile = $this->projectPath . '/apps/apps.json';
// Set app type.
$this->appType = $package->getType();
@@ -93,6 +145,46 @@ public function __construct(PackageEvent $packageEvent) {
// Get package name.
$path_components = explode('/', rtrim($relativeAppPath, '/'));
$this->packageName = end($path_components);
+
+ // Check if in CI mode. Used by Github workflow.
+ $this->ciMode = empty(getenv('HEALTH_CI_MODE')) ? FALSE : TRUE;
+ }
+
+ /**
+ * Selects the correct package manager to use to install the application.
+ *
+ * @return string
+ * The name of the package manager to use (e.g. npm, yarn)
+ */
+ protected function choosePackageManager() {
+ if ($this->ciMode === TRUE) {
+ // Use npm as the package manager when running in CI mode.
+ $response = $this::PACKAGE_MANAGER_NPM;
+ }
+ else {
+ $response = $this
+ ->consoleIO
+ ->ask("\n\nLock files have been found for both npm and yarn package manager systems. Which package manager would you like to use? [enter the number of the package manager to use]\n\n[1] yarn\n[2] npm\n\n");
+ while (!in_array($response, [1, 2])) {
+ $response = $this
+ ->consoleIO
+ ->ask("Not a valid selection. Which package manager would you like to use? [enter the number of the package manager to use]\n\n[1] yarn\n[2]npm\n\n");
+ }
+ }
+
+ switch ($response) {
+
+ case $this::PACKAGE_MANAGER_YARN:
+ $packageManager = 'yarn';
+ break;
+
+ case $this::PACKAGE_MANAGER_NPM:
+ $packageManager = 'npm';
+ break;
+
+ }
+
+ return $packageManager;
}
/**
@@ -107,10 +199,14 @@ public function __construct(PackageEvent $packageEvent) {
protected function executeCommand(string $command, string $success_message = '[DONE]') {
exec($command, $output, $exit_code);
if ($exit_code == 0) {
- $this->consoleIO->write($success_message);
+ $this
+ ->consoleIO
+ ->write($success_message);
}
else {
- $this->consoleIO->writeError("[WARNING] Something went wrong whilst running `$command`. Exit code: $exit_code");
+ $this
+ ->consoleIO
+ ->writeError("[WARNING] Something went wrong whilst running `$command`. Exit code: $exit_code");
}
}
@@ -124,7 +220,9 @@ protected function postIntegrationCleanup() {
'.git',
'.gitignore',
];
- $this->consoleIO->write("Checking if project uses Git... ");
+ $this
+ ->consoleIO
+ ->write("Checking if project uses Git... ");
foreach ($git_related_files_folders as $file) {
$git_file_path = $this->appPath . $file;
if (file_exists($git_file_path)) {
@@ -132,7 +230,40 @@ protected function postIntegrationCleanup() {
$this->executeCommand($command, "Removing " . $git_file_path);
}
}
- $this->consoleIO->write("[DONE]");
+ $this
+ ->consoleIO
+ ->write("[DONE]");
+ }
+
+ /**
+ * Get the content of the app registry file.
+ *
+ * @return array
+ * Registry of currently installed apps listed in the app registry file.
+ */
+ protected function getAppRegistry() {
+ $data = file_get_contents($this->appRegistryFile);
+
+ return json_decode($data);
+ }
+
+ /**
+ * Write list of installed apps to the app registry file.
+ *
+ * @param array $data
+ * List of installed apps.
+ */
+ protected function setAppRegistry(array $data) {
+ $success = TRUE;
+ $data = json_encode($data);
+ if (file_put_contents($this->appRegistryFile, $data) === FALSE) {
+ $this
+ ->consoleIO
+ ->write('Error setting app registry.');
+ $success = FALSE;
+ }
+
+ return $success;
}
/**
@@ -175,8 +306,40 @@ public static function isApp(CompletePackage $package) {
* @access protected
*/
protected function processApp() {
+ // Confirm which theme the app should be added to.
+ $themePaths = glob($this->projectPath . '/themes/*', GLOB_ONLYDIR);
+ foreach ($themePaths as $index => $path) {
+ $pathParts = explode('/', $path);
+ $name = end($pathParts);
+ $options[++$index] = '[' . $index . '] ' . $name;
+ $themeMapping[$index] = $name;
+ }
+
+ if ($this->ciMode) {
+ $theme_name = 'health';
+ $index = array_search($theme_name, $themeMapping);
+ $theme = $themeMapping[$index];
+ }
+ else {
+ $text = "In which theme should the app or tool be installed? Enter the number corresponding to the theme:\n\n";
+ $text .= implode("\n", $options);
+ $text .= "\n\n";
+ $response = $this
+ ->consoleIO
+ ->ask($text);
+ while (!array_key_exists($response, $themeMapping)) {
+ $response = $this
+ ->consoleIO
+ ->ask("Not a valid choice.\n\n" . $text);
+ }
+ $theme = $themeMapping[$response];
+ }
+ $this->destinationTheme = $theme;
+
// Confirm if app is a production or development build.
- $isProductionBuild = $this->consoleIO->askConfirmation("Is this a production build? (Y, n): ", TRUE);
+ $isProductionBuild = $this
+ ->consoleIO
+ ->askConfirmation("Is this a production build? (Y, n) [default = Y]: ", TRUE);
$this->isProductionBuild = (empty($isProductionBuild)) ? FALSE : TRUE;
// Check if recognised app and process according to type.
@@ -198,8 +361,67 @@ protected function processApp() {
// Generic postintegration cleanup tasks.
$this->postIntegrationCleanup();
- // Display the location of the newly installed app.
- $this->consoleIO->write("App has been installed in following location: $this->appPath");
+ // Move application directory to destination theme.
+ $sourcePath = rtrim($this->appPath, '/');
+ $themeAppsPath = implode('/', [
+ $this->projectPath,
+ 'themes',
+ $this->destinationTheme,
+ 'apps',
+ ]);
+ $destinationPath = implode('/', [
+ $themeAppsPath,
+ $this->packageName,
+ ]);
+
+ try {
+ // Add app information to the app registry.
+ $app_registry = $this->getAppRegistry();
+ $app_registry[] = [
+ 'app' => $this->packageName,
+ 'theme' => $this->destinationTheme,
+ ];
+ $this->setAppRegistry($app_registry);
+
+ // Move app files into destination theme.
+ if (!file_exists($themeAppsPath)) {
+ mkdir($themeAppsPath, 0766, TRUE);
+ }
+ if (rename($sourcePath, $destinationPath) === TRUE) {
+ $this
+ ->consoleIO
+ ->write('Moved app to the ' . $this->destinationTheme . ' theme.');
+ }
+ else {
+ throw new AppIntegrationException('Failed to move app to the ' . $this->destinationTheme . ' theme.');
+ }
+ // Create a stub in the original location. When removing a Composer
+ // package, Composer will check the package exists in the originally
+ // installed location. If this location does not exist then it will not
+ // trigger any package uninstall hooks.
+ if (mkdir($sourcePath) === TRUE) {
+ $stubFile = $sourcePath . '/.gitkeep';
+ file_put_contents($stubFile, '');
+ }
+ else {
+ throw new AppIntegrationException('Failed to create original package stub.');
+ };
+
+ // Display the location of the newly installed app.
+ if (file_exists($destinationPath)) {
+ $this
+ ->consoleIO
+ ->write('App has been installed in the following location: ' . $destinationPath);
+ }
+ else {
+ throw new AppIntegrationException('Oops, the app cannot be found in the expected location.');
+ }
+ }
+ catch (AppIntegrationException $e) {
+ $this
+ ->consoleIO
+ ->write('ERROR: ' . $e->getMessage());
+ }
}
/**
@@ -209,19 +431,30 @@ protected function processAngularApp() {
// @todo Add processing specific to Angular based apps.
}
+ /**
+ * Process default app type.
+ */
protected function processDefaultApp() {
// Install dependencies.
- $this->consoleIO->write("Checking for NPM dependencies... ");
+ $this
+ ->consoleIO
+ ->write("Checking for NPM dependencies... ");
if (file_exists($this->appPath . 'package.json')) {
- $installNpmDependencies = $this->consoleIO->askConfirmation("A package.json file has been detected. Would you like to install any NPM dependencies? (Y, n): ", TRUE);
+ $installNpmDependencies = $this
+ ->consoleIO
+ ->askConfirmation("A package.json file has been detected. Would you like to install any NPM dependencies? (Y, n): [default = Y]", TRUE);
if ($installNpmDependencies) {
- $this->consoleIO->write("Installing dependencies... ");
+ $this
+ ->consoleIO
+ ->write("Installing dependencies... ");
$command = 'cd ' . escapeshellcmd($this->appPath) . ' && npm install';
$this->executeCommand($command);
}
else {
- $this->consoleIO->write("Skipping installation of any NPM dependencies.");
+ $this
+ ->consoleIO
+ ->write("Skipping installation of any NPM dependencies.");
}
}
}
@@ -230,34 +463,81 @@ protected function processDefaultApp() {
* Post install/update processing for Angular apps.
*/
protected function processReactApp() {
- $removeCss = $this->consoleIO->askConfirmation("Remove static CSS links from project? (y, N): ", FALSE);
- $this->removeCss = (empty($removeCss)) ? FALSE : TRUE;
-
- // Add relevant environment variables.
- $this->setReactEnvironmentalVariables();
+ try {
+ $removeCss = $this
+ ->consoleIO
+ ->askConfirmation("Remove static CSS links from project? (y, N): [default = N]", FALSE);
+ $this->removeCss = (empty($removeCss)) ? FALSE : TRUE;
+
+ // Add relevant environment variables.
+ $this->setReactEnvironmentalVariables();
+
+ // Install dependencies.
+ $commands = [
+ 'npm' => [
+ 'install' => 'npm install',
+ 'build' => 'npm run build',
+ ],
+ 'yarn' => [
+ 'install' => 'yarn install',
+ 'build' => 'yarn build',
+ ],
+ ];
+ if (file_exists($this->appPath . 'yarn.lock') && !file_exists($this->appPath . 'package-lock.json')) {
+ $this->dependencyManager = 'yarn';
+ }
+ elseif (file_exists($this->appPath . 'package-lock.json') && !file_exists($this->appPath . 'yarn.lock')) {
+ $this->dependencyManager = 'npm';
+ }
+ elseif (file_exists($this->appPath . 'package-lock.json') && file_exists($this->appPath . 'yarn.lock')) {
+ $this->dependencyManager = $this->choosePackageManager();
+ }
+ elseif (file_exists($this->appPath . 'yarn.json') && !file_exists($this->appPath . 'package.json')) {
+ $this->dependencyManager = 'yarn';
+ }
+ elseif (file_exists($this->appPath . 'package.json') && !file_exists($this->appPath . 'yarn.json')) {
+ $this->dependencyManager = 'npm';
+ }
+ elseif (file_exists($this->appPath . 'package.json') && file_exists($this->appPath . 'yarn.json')) {
+ $this->dependencyManager = $this->choosePackageManager();
+ }
+ else {
+ throw new AppIntegrationException('Could not find yarn or npm configuration for this project.');
+ }
- // Install dependencies.
- if (file_exists($this->appPath . 'package.json')) {
- $this->consoleIO->write("Installing dependencies... ");
- $command = 'cd ' . escapeshellcmd($this->appPath) . ' && npm install';
+ $this
+ ->consoleIO
+ ->write('Installing dependencies using ' . $this->dependencyManager . ' ...');
+ $command = 'cd ' . escapeshellcmd($this->appPath) . ' && ' . $commands[$this->dependencyManager]['install'];
$this->executeCommand($command);
- }
- // Build app.
- $this->consoleIO->write("Building React project... ");
- if (file_exists($this->appPath . '.env.local')) {
- $command = 'cd ' . escapeshellcmd($this->appPath) . ' && npm run build';
- $this->executeCommand($command);
- }
+ // Build app.
+ $this
+ ->consoleIO
+ ->write('Building React project using ' . $this->dependencyManager . ' ...');
+ if (file_exists($this->appPath . '.env.local')) {
+ $command = 'cd ' . escapeshellcmd($this->appPath) . ' && ' . $commands[$this->dependencyManager]['build'];
+ $this->executeCommand($command);
+ }
- // Remove source files.
- if ($this->isProductionBuild && file_exists($this->appPath . "src")) {
- $this->consoleIO->write("Remove React project source files... ", FALSE);
- $command = 'cd ' . escapeshellcmd($this->appPath) . ' && rm -Rf ./src';
- $this->executeCommand($command);
+ // Remove source files.
+ if ($this->isProductionBuild && file_exists($this->appPath . "src")) {
+ $this
+ ->consoleIO
+ ->write("Remove React project source files... ", FALSE);
+ $command = 'cd ' . escapeshellcmd($this->appPath) . ' && rm -Rf ./src';
+ $this->executeCommand($command);
+ }
+ else {
+ $this
+ ->consoleIO
+ ->write("Non-production build. React project source files have been retained.");
+ }
}
- else {
- $this->consoleIO->write("Non-production build. React project source files have been retained.");
+ catch (AppIntegrationException $e) {
+ $this
+ ->consoleIO
+ ->write('ERROR: ' . $e->getMessage());
}
// Remove static assets.
@@ -271,13 +551,28 @@ protected function processReactApp() {
* Instance of PackageEvent class.
*/
public static function postPackageInstall(PackageEvent $event) {
- $package = $event->getOperation()->getPackage();
+ $package = $event
+ ->getOperation()
+ ->getPackage();
if (static::isApp($package)) {
$app = new App($event);
$app->processApp();
}
}
+ /**
+ * Package post uninstall handler.
+ */
+ public static function postPackageUninstall(PackageEvent $event) {
+ $package = $event
+ ->getOperation()
+ ->getPackage();
+ if (static::isApp($package)) {
+ $app = new App($event);
+ $app->remove();
+ }
+ }
+
/**
* Remove static assets from relevant build files.
*
@@ -297,8 +592,10 @@ protected function processAppAssets() {
$indexFilePath = $this->appPath . 'build/index.html';
$static_css_links = [];
if (file_exists($indexFilePath)) {
- $this->consoleIO->write('Removing elements form index.html file... ', FALSE);
- $dom = new DOMDocument();
+ $this
+ ->consoleIO
+ ->write('Removing elements form index.html file... ', FALSE);
+ $dom = new \DOMDocument();
if (@$dom->loadHTMLFile($indexFilePath)) {
$links = $dom->getElementsByTagName('link');
foreach ($links as $link) {
@@ -308,18 +605,26 @@ protected function processAppAssets() {
}
}
foreach ($static_css_links as $link) {
- $link->parentNode->removeChild($link);
+ $link
+ ->parentNode
+ ->removeChild($link);
}
// Write modified markup back to index.html file.
if (!empty(file_put_contents($indexFilePath, $dom->saveHTML()))) {
- $this->consoleIO->write("[DONE]");
+ $this
+ ->consoleIO
+ ->write("[DONE]");
}
else {
- $this->consoleIO->write("[FAILED]\nError occurred when trying to write modified markup to index.html file.");
+ $this
+ ->consoleIO
+ ->write("[FAILED]\nError occurred when trying to write modified markup to index.html file.");
}
}
else {
- $this->consoleIO->write("[FAILED]\nError occurred while reading index.html file.");
+ $this
+ ->consoleIO
+ ->write("[FAILED]\nError occurred while reading index.html file.");
}
}
}
@@ -327,6 +632,73 @@ protected function processAppAssets() {
}
}
+ /**
+ * Remove app from relevant theme.
+ */
+ protected function remove() {
+ $app_registry = $this->getAppRegistry();
+ foreach ($app_registry as $index => $item) {
+ if ($item->app === $this->packageName) {
+ $app_path = implode('/', [
+ $this->projectPath,
+ 'themes',
+ $item->theme,
+ 'apps',
+ $this->packageName,
+ ]);
+
+ try {
+ // Remove app files from theme.
+ if (file_exists($app_path)) {
+ if ($this->removeDirectory($app_path)) {
+ $this
+ ->consoleIO
+ ->write($this->packageName . ' app files have been successfully removed.');
+ }
+ else {
+ throw new AppIntegrationException('Failed to remove ' . $this->packageName . ' app files.');
+ }
+ }
+
+ // Remove package from app registry.
+ unset($app_registry[$index]);
+ }
+ catch (AppIntegrationException $e) {
+ $this
+ ->consoleIO
+ ->write($e->getMessage());
+ }
+ }
+ }
+ $this->setAppRegistry($app_registry);
+ }
+
+ /**
+ * Remove a directory including all files and sub-directories.
+ *
+ * The code for this function is derived from
+ * https://www.beliefmedia.com.au/php-delete-directory-contents.
+ *
+ * @param string $dir
+ * Full path to the directory to be removed.
+ *
+ * @return bool
+ * Whether or not the directory was successfully removed.
+ */
+ protected function removeDirectory($dir) {
+ if (is_dir($dir)) {
+ $objects = scandir($dir);
+ foreach ($objects as $object) {
+ if ($object != '.' && $object != '..') {
+ (filetype($dir . '/' . $object) == 'dir') ? $this->removeDirectory($dir . '/' . $object) : unlink($dir . '/' . $object);
+ }
+ }
+ reset($objects);
+
+ return rmdir($dir) ? TRUE : FALSE;
+ }
+ }
+
/**
* Create environmental variables file for React project.
*/
@@ -337,8 +709,8 @@ protected function setReactEnvironmentalVariables() {
if ($this->isProductionBuild) {
// Set environmental variables for production build.
- $variables['REACT_APP_PATH'] = '/themes/custom/health/apps/' . $this->packageName . '/build';
- $variables['PUBLIC_URL'] = '/themes/custom/health/apps/' . $this->packageName . '/build';
+ $variables['REACT_APP_PATH'] = '/themes/custom/' . $this->destinationTheme . '/apps/' . $this->packageName . '/build';
+ $variables['PUBLIC_URL'] = '/themes/custom/' . $this->destinationTheme . '/apps/' . $this->packageName . '/build';
$variables['REACT_APP_PRODUCTION'] = 1;
}
else {
@@ -351,11 +723,13 @@ protected function setReactEnvironmentalVariables() {
}
try {
if (!empty($variables) && !file_put_contents($localEnvFilePath, $output)) {
- throw new Exception('Error creating .env.local file');
+ throw new AppIntegrationException('Error creating .env.local file');
}
}
- catch (Exception $e) {
- $this->consoleIO->write($e->getMessage());
+ catch (AppIntegrationException $e) {
+ $this
+ ->consoleIO
+ ->write($e->getMessage());
}
}
diff --git a/src/Health/AppIntegrationException.php b/src/Health/AppIntegrationException.php
new file mode 100644
index 00000000..d3aae4ef
--- /dev/null
+++ b/src/Health/AppIntegrationException.php
@@ -0,0 +1,8 @@
+consoleIO->write($success_message);
+ $this
+ ->consoleIO
+ ->write($success_message);
}
else {
- $this->consoleIO->writeError("[WARNING] Something went wrong whilst running `$command`. Exit code: $exit_code");
+ $this
+ ->consoleIO
+ ->writeError("[WARNING] Something went wrong whilst running `$command`. Exit code: $exit_code");
}
}
@@ -91,7 +95,9 @@ protected function executeCommand(string $command, string $success_message = '[D
* Instance of PackageEvent class.
*/
public static function postPackageInstall(PackageEvent $packageEvent) {
- $package = $packageEvent->getOperation()->getPackage();
+ $package = $packageEvent
+ ->getOperation()
+ ->getPackage();
$packageTypes = [
'health-theme',
];
@@ -113,7 +119,9 @@ protected function processInstalledPackageFiles() {
'.git',
'.gitignore',
];
- $this->consoleIO->write("\nChecking if installed package is a Git repository... ");
+ $this
+ ->consoleIO
+ ->write("\nChecking if installed package is a Git repository... ");
foreach ($git_related_files_folders as $file) {
$git_file_path = $this->packagePath . $file;
if (file_exists($git_file_path)) {
@@ -121,7 +129,9 @@ protected function processInstalledPackageFiles() {
$this->executeCommand($command, "Removing " . $git_file_path);
}
}
- $this->consoleIO->write("[DONE]\n");
+ $this
+ ->consoleIO
+ ->write("[DONE]\n");
}
}
diff --git a/themes/health/health.theme b/themes/health/health.theme
index 0e9f69e0..a2b6b70b 100644
--- a/themes/health/health.theme
+++ b/themes/health/health.theme
@@ -2055,6 +2055,7 @@ function health_preprocess_file_link(array &$variables) {
*/
function health_preprocess_paragraph(array &$variables) {
$paragraph = $variables['paragraph'];
+
// Make background colour information available to template. Include colour's
// hexcode and correspondin light/dark theme information.
try {
@@ -2308,6 +2309,10 @@ function health_preprocess_field__paragraph__field_h_title(array &$variables) {
function health_preprocess_paragraph__para_h_app_or_tool(array &$variables) {
$paragraph = $variables['paragraph'];
if ($paragraph instanceof ParagraphInterface) {
+ $theme_manager = \Drupal::service('theme.manager');
+ $current_theme = $theme_manager
+ ->getActiveTheme()
+ ->getName();
// Make CSS class for app width available to template.
$variables['app_width_class'] = '';
if ($paragraph->hasField('field_h_app_width') && $paragraph->get('field_h_app_width')
@@ -2321,7 +2326,7 @@ function health_preprocess_paragraph__para_h_app_or_tool(array &$variables) {
->isEmpty() === FALSE) {
$path = str_replace('../', '', $paragraph->get('field_h_app_path')->value);
// Remove any leading or trailing slashes.
- $location = drupal_get_path('theme', 'health') . '/apps/' . $path;
+ $location = drupal_get_path('theme', $current_theme) . '/apps/' . $path;
$index_file = $paragraph->get('field_h_app_index_file')->value;
if ($realpath = realpath($location . '/' . $index_file)) {